Compare commits

...

4 Commits

Author SHA1 Message Date
2f74732924 Coding style 2019-12-04 17:10:06 +01:00
c1e8637516 Coding Styles 2019-12-03 18:44:25 +01:00
186792fde8 Coding styles 2019-12-03 18:43:54 +01:00
d75c3bc73f Add LICENSE and README.md 2019-05-29 19:25:41 +02:00
183 changed files with 33474 additions and 35028 deletions

31
LICENSE Normal file
View File

@ -0,0 +1,31 @@
MIT License
Copyright (c) 2016 Unosquare
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
This software contains a compiled, unmodified version of the WiringPi library
WiringPi is a GPIO access library written in C for the BCM2835 used in the
Raspberry Pi. Its released under the GNU LGPLv3 license and is usable from C
and C++ and many other languages with suitable wrappers. A program that contains
no derivative of any portion of the Library, but is designed to work with
the Library by being compiled or linked with it, is called a "work that uses
the Library". Such a work, in isolation, is not a derivative work of the Library,
and therefore falls outside the scope of this License. Raspberry IO is then,
by definition, "work that uses the Library"

5
README.md Normal file
View File

@ -0,0 +1,5 @@
# UnoSquare
## RaspberryIO
Based on https://github.com/unosquare/raspberryio
## SWAN
Based on https://github.com/unosquare/swan

View File

@ -1,134 +1,140 @@
namespace Unosquare.RaspberryIO.Camera using Unosquare.Swan;
{ using System;
using Swan; using System.Linq;
using System;
using System.Linq; namespace Unosquare.RaspberryIO.Camera {
/// <summary>
/// A simple RGB color class to represent colors in RGB and YUV colorspaces.
/// </summary>
public class CameraColor {
/// <summary> /// <summary>
/// A simple RGB color class to represent colors in RGB and YUV colorspaces. /// Initializes a new instance of the <see cref="CameraColor"/> class.
/// </summary> /// </summary>
public class CameraColor /// <param name="r">The red.</param>
{ /// <param name="g">The green.</param>
/// <summary> /// <param name="b">The blue.</param>
/// Initializes a new instance of the <see cref="CameraColor"/> class. public CameraColor(Int32 r, Int32 g, Int32 b)
/// </summary> : this(r, g, b, String.Empty) {
/// <param name="r">The red.</param> }
/// <param name="g">The green.</param>
/// <param name="b">The blue.</param> /// <summary>
public CameraColor(int r, int g, int b) /// Initializes a new instance of the <see cref="CameraColor"/> class.
: this(r, g, b, string.Empty) /// </summary>
{ /// <param name="r">The red.</param>
} /// <param name="g">The green.</param>
/// <param name="b">The blue.</param>
/// <summary> /// <param name="name">The well-known color name.</param>
/// Initializes a new instance of the <see cref="CameraColor"/> class. public CameraColor(Int32 r, Int32 g, Int32 b, String name) {
/// </summary> this.RGB = new[] { Convert.ToByte(r.Clamp(0, 255)), Convert.ToByte(g.Clamp(0, 255)), Convert.ToByte(b.Clamp(0, 255)) };
/// <param name="r">The red.</param>
/// <param name="g">The green.</param> Single y = this.R * .299000f + this.G * .587000f + this.B * .114000f;
/// <param name="b">The blue.</param> Single u = this.R * -.168736f + this.G * -.331264f + this.B * .500000f + 128f;
/// <param name="name">The well-known color name.</param> Single v = this.R * .500000f + this.G * -.418688f + this.B * -.081312f + 128f;
public CameraColor(int r, int g, int b, string name)
{ this.YUV = new Byte[] { (Byte)y.Clamp(0, 255), (Byte)u.Clamp(0, 255), (Byte)v.Clamp(0, 255) };
RGB = new[] { Convert.ToByte(r.Clamp(0, 255)), Convert.ToByte(g.Clamp(0, 255)), Convert.ToByte(b.Clamp(0, 255)) }; this.Name = name;
}
var y = (R * .299000f) + (G * .587000f) + (B * .114000f);
var u = (R * -.168736f) + (G * -.331264f) + (B * .500000f) + 128f; #region Static Definitions
var v = (R * .500000f) + (G * -.418688f) + (B * -.081312f) + 128f;
/// <summary>
YUV = new byte[] { (byte)y.Clamp(0, 255), (byte)u.Clamp(0, 255), (byte)v.Clamp(0, 255) }; /// Gets the predefined white color.
Name = name; /// </summary>
} public static CameraColor White => new CameraColor(255, 255, 255, nameof(White));
#region Static Definitions /// <summary>
/// Gets the predefined red color.
/// <summary> /// </summary>
/// Gets the predefined white color. public static CameraColor Red => new CameraColor(255, 0, 0, nameof(Red));
/// </summary>
public static CameraColor White => new CameraColor(255, 255, 255, nameof(White)); /// <summary>
/// Gets the predefined green color.
/// <summary> /// </summary>
/// Gets the predefined red color. public static CameraColor Green => new CameraColor(0, 255, 0, nameof(Green));
/// </summary>
public static CameraColor Red => new CameraColor(255, 0, 0, nameof(Red)); /// <summary>
/// Gets the predefined blue color.
/// <summary> /// </summary>
/// Gets the predefined green color. public static CameraColor Blue => new CameraColor(0, 0, 255, nameof(Blue));
/// </summary>
public static CameraColor Green => new CameraColor(0, 255, 0, nameof(Green)); /// <summary>
/// Gets the predefined black color.
/// <summary> /// </summary>
/// Gets the predefined blue color. public static CameraColor Black => new CameraColor(0, 0, 0, nameof(Black));
/// </summary>
public static CameraColor Blue => new CameraColor(0, 0, 255, nameof(Blue)); #endregion
/// <summary> /// <summary>
/// Gets the predefined black color. /// Gets the well-known color name.
/// </summary> /// </summary>
public static CameraColor Black => new CameraColor(0, 0, 0, nameof(Black)); public String Name {
get;
#endregion }
/// <summary> /// <summary>
/// Gets the well-known color name. /// Gets the red byte.
/// </summary> /// </summary>
public string Name { get; } public Byte R => this.RGB[0];
/// <summary> /// <summary>
/// Gets the red byte. /// Gets the green byte.
/// </summary> /// </summary>
public byte R => RGB[0]; public Byte G => this.RGB[1];
/// <summary> /// <summary>
/// Gets the green byte. /// Gets the blue byte.
/// </summary> /// </summary>
public byte G => RGB[1]; public Byte B => this.RGB[2];
/// <summary> /// <summary>
/// Gets the blue byte. /// Gets the RGB byte array (3 bytes).
/// </summary> /// </summary>
public byte B => RGB[2]; public Byte[] RGB {
get;
/// <summary> }
/// Gets the RGB byte array (3 bytes).
/// </summary> /// <summary>
public byte[] RGB { get; } /// Gets the YUV byte array (3 bytes).
/// </summary>
/// <summary> public Byte[] YUV {
/// Gets the YUV byte array (3 bytes). get;
/// </summary> }
public byte[] YUV { get; }
/// <summary>
/// <summary> /// Returns a hexadecimal representation of the RGB byte array.
/// Returns a hexadecimal representation of the RGB byte array. /// Preceded by 0x and all in lowercase
/// Preceded by 0x and all in lowercase /// </summary>
/// </summary> /// <param name="reverse">if set to <c>true</c> [reverse].</param>
/// <param name="reverse">if set to <c>true</c> [reverse].</param> /// <returns>A string</returns>
/// <returns>A string</returns> public String ToRgbHex(Boolean reverse) {
public string ToRgbHex(bool reverse) Byte[] data = this.RGB.ToArray();
{ if(reverse) {
var data = RGB.ToArray(); Array.Reverse(data);
if (reverse) Array.Reverse(data); }
return ToHex(data);
} return ToHex(data);
}
/// <summary>
/// Returns a hexadecimal representation of the YUV byte array. /// <summary>
/// Preceded by 0x and all in lowercase /// Returns a hexadecimal representation of the YUV byte array.
/// </summary> /// Preceded by 0x and all in lowercase
/// <param name="reverse">if set to <c>true</c> [reverse].</param> /// </summary>
/// <returns>A string</returns> /// <param name="reverse">if set to <c>true</c> [reverse].</param>
public string ToYuvHex(bool reverse) /// <returns>A string</returns>
{ public String ToYuvHex(Boolean reverse) {
var data = YUV.ToArray(); Byte[] data = this.YUV.ToArray();
if (reverse) Array.Reverse(data); if(reverse) {
return ToHex(data); Array.Reverse(data);
} }
/// <summary> return ToHex(data);
/// Returns a hexadecimal representation of the data byte array }
/// </summary>
/// <param name="data">The data.</param> /// <summary>
/// <returns>A string</returns> /// Returns a hexadecimal representation of the data byte array
private static string ToHex(byte[] data) => $"0x{BitConverter.ToString(data).Replace("-", string.Empty).ToLowerInvariant()}"; /// </summary>
} /// <param name="data">The data.</param>
/// <returns>A string</returns>
private static String ToHex(Byte[] data) => $"0x{BitConverter.ToString(data).Replace("-", String.Empty).ToLowerInvariant()}";
}
} }

View File

@ -1,216 +1,190 @@
namespace Unosquare.RaspberryIO.Camera using Unosquare.Swan.Abstractions;
{ using System;
using Swan.Abstractions; using Unosquare.Swan.Components;
using System; using System.IO;
using Swan.Components; using System.Threading;
using System.IO; using System.Threading.Tasks;
using System.Threading;
using System.Threading.Tasks; namespace Unosquare.RaspberryIO.Camera {
/// <summary>
/// The Raspberry Pi's camera controller wrapping raspistill and raspivid programs.
/// This class is a singleton
/// </summary>
public class CameraController : SingletonBase<CameraController> {
#region Private Declarations
private static readonly ManualResetEventSlim OperationDone = new ManualResetEventSlim(true);
private static readonly Object SyncRoot = new Object();
private static CancellationTokenSource _videoTokenSource = new CancellationTokenSource();
private static Task<Task> _videoStreamTask;
#endregion
#region Properties
/// <summary> /// <summary>
/// The Raspberry Pi's camera controller wrapping raspistill and raspivid programs. /// Gets a value indicating whether the camera module is busy.
/// This class is a singleton
/// </summary> /// </summary>
public class CameraController : SingletonBase<CameraController> /// <value>
{ /// <c>true</c> if this instance is busy; otherwise, <c>false</c>.
#region Private Declarations /// </value>
public Boolean IsBusy => OperationDone.IsSet == false;
private static readonly ManualResetEventSlim OperationDone = new ManualResetEventSlim(true);
private static readonly object SyncRoot = new object(); #endregion
private static CancellationTokenSource _videoTokenSource = new CancellationTokenSource();
private static Task<Task> _videoStreamTask; #region Image Capture Methods
#endregion /// <summary>
/// Captures an image asynchronously.
#region Properties /// </summary>
/// <param name="settings">The settings.</param>
/// <summary> /// <param name="ct">The ct.</param>
/// Gets a value indicating whether the camera module is busy. /// <returns>The image bytes</returns>
/// </summary> /// <exception cref="InvalidOperationException">Cannot use camera module because it is currently busy.</exception>
/// <value> [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
/// <c>true</c> if this instance is busy; otherwise, <c>false</c>. public async Task<Byte[]> CaptureImageAsync(CameraStillSettings settings, CancellationToken ct = default) {
/// </value> if(Instance.IsBusy) {
public bool IsBusy => OperationDone.IsSet == false; throw new InvalidOperationException("Cannot use camera module because it is currently busy.");
}
#endregion
if(settings.CaptureTimeoutMilliseconds <= 0) {
#region Image Capture Methods throw new ArgumentException($"{nameof(settings.CaptureTimeoutMilliseconds)} needs to be greater than 0");
}
/// <summary>
/// Captures an image asynchronously. try {
/// </summary> OperationDone.Reset();
/// <param name="settings">The settings.</param>
/// <param name="ct">The ct.</param> MemoryStream output = new MemoryStream();
/// <returns>The image bytes</returns> Int32 exitCode = await ProcessRunner.RunProcessAsync(
/// <exception cref="InvalidOperationException">Cannot use camera module because it is currently busy.</exception> settings.CommandName,
public async Task<byte[]> CaptureImageAsync(CameraStillSettings settings, CancellationToken ct = default) settings.CreateProcessArguments(),
{ (data, proc) => output.Write(data, 0, data.Length),
if (Instance.IsBusy) null,
throw new InvalidOperationException("Cannot use camera module because it is currently busy."); true,
ct);
if (settings.CaptureTimeoutMilliseconds <= 0)
throw new ArgumentException($"{nameof(settings.CaptureTimeoutMilliseconds)} needs to be greater than 0"); return exitCode != 0 ? new Byte[] { } : output.ToArray();
} finally {
try OperationDone.Set();
{ }
OperationDone.Reset(); }
var output = new MemoryStream(); /// <summary>
var exitCode = await ProcessRunner.RunProcessAsync( /// Captures an image.
settings.CommandName, /// </summary>
settings.CreateProcessArguments(), /// <param name="settings">The settings.</param>
(data, proc) => /// <returns>The image bytes</returns>
{ public Byte[] CaptureImage(CameraStillSettings settings) => this.CaptureImageAsync(settings).GetAwaiter().GetResult();
output.Write(data, 0, data.Length);
}, /// <summary>
null, /// Captures a JPEG encoded image asynchronously at 90% quality.
true, /// </summary>
ct); /// <param name="width">The width.</param>
/// <param name="height">The height.</param>
return exitCode != 0 ? new byte[] { } : output.ToArray(); /// <param name="ct">The ct.</param>
} /// <returns>The image bytes</returns>
finally public Task<Byte[]> CaptureImageJpegAsync(Int32 width, Int32 height, CancellationToken ct = default) {
{ CameraStillSettings settings = new CameraStillSettings {
OperationDone.Set(); CaptureWidth = width,
} CaptureHeight = height,
} CaptureJpegQuality = 90,
CaptureTimeoutMilliseconds = 300,
/// <summary> };
/// Captures an image.
/// </summary> return this.CaptureImageAsync(settings, ct);
/// <param name="settings">The settings.</param> }
/// <returns>The image bytes</returns>
public byte[] CaptureImage(CameraStillSettings settings) /// <summary>
{ /// Captures a JPEG encoded image at 90% quality.
return CaptureImageAsync(settings).GetAwaiter().GetResult(); /// </summary>
} /// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <summary> /// <returns>The image bytes</returns>
/// Captures a JPEG encoded image asynchronously at 90% quality. public Byte[] CaptureImageJpeg(Int32 width, Int32 height) => this.CaptureImageJpegAsync(width, height).GetAwaiter().GetResult();
/// </summary>
/// <param name="width">The width.</param> #endregion
/// <param name="height">The height.</param>
/// <param name="ct">The ct.</param> #region Video Capture Methods
/// <returns>The image bytes</returns>
public Task<byte[]> CaptureImageJpegAsync(int width, int height, CancellationToken ct = default) /// <summary>
{ /// Opens the video stream with a timeout of 0 (running indefinitely) at 1080p resolution, variable bitrate and 25 FPS.
var settings = new CameraStillSettings /// No preview is shown
{ /// </summary>
CaptureWidth = width, /// <param name="onDataCallback">The on data callback.</param>
CaptureHeight = height, /// <param name="onExitCallback">The on exit callback.</param>
CaptureJpegQuality = 90, public void OpenVideoStream(Action<Byte[]> onDataCallback, Action onExitCallback = null) {
CaptureTimeoutMilliseconds = 300, CameraVideoSettings settings = new CameraVideoSettings {
}; CaptureTimeoutMilliseconds = 0,
CaptureDisplayPreview = false,
return CaptureImageAsync(settings, ct); CaptureWidth = 1920,
} CaptureHeight = 1080
};
/// <summary>
/// Captures a JPEG encoded image at 90% quality. this.OpenVideoStream(settings, onDataCallback, onExitCallback);
/// </summary> }
/// <param name="width">The width.</param>
/// <param name="height">The height.</param> /// <summary>
/// <returns>The image bytes</returns> /// Opens the video stream with the supplied settings. Capture Timeout Milliseconds has to be 0 or greater
public byte[] CaptureImageJpeg(int width, int height) => CaptureImageJpegAsync(width, height).GetAwaiter().GetResult(); /// </summary>
/// <param name="settings">The settings.</param>
#endregion /// <param name="onDataCallback">The on data callback.</param>
/// <param name="onExitCallback">The on exit callback.</param>
#region Video Capture Methods /// <exception cref="InvalidOperationException">Cannot use camera module because it is currently busy.</exception>
/// <exception cref="ArgumentException">CaptureTimeoutMilliseconds</exception>
/// <summary> public void OpenVideoStream(CameraVideoSettings settings, Action<Byte[]> onDataCallback, Action onExitCallback) {
/// Opens the video stream with a timeout of 0 (running indefinitely) at 1080p resolution, variable bitrate and 25 FPS. if(Instance.IsBusy) {
/// No preview is shown throw new InvalidOperationException("Cannot use camera module because it is currently busy.");
/// </summary> }
/// <param name="onDataCallback">The on data callback.</param>
/// <param name="onExitCallback">The on exit callback.</param> if(settings.CaptureTimeoutMilliseconds < 0) {
public void OpenVideoStream(Action<byte[]> onDataCallback, Action onExitCallback = null) throw new ArgumentException($"{nameof(settings.CaptureTimeoutMilliseconds)} needs to be greater than or equal to 0");
{ }
var settings = new CameraVideoSettings
{ try {
CaptureTimeoutMilliseconds = 0, OperationDone.Reset();
CaptureDisplayPreview = false, _videoStreamTask = Task.Factory.StartNew(() => VideoWorkerDoWork(settings, onDataCallback, onExitCallback), _videoTokenSource.Token);
CaptureWidth = 1920, } catch {
CaptureHeight = 1080 OperationDone.Set();
}; throw;
}
OpenVideoStream(settings, onDataCallback, onExitCallback); }
}
/// <summary>
/// <summary> /// Closes the video stream of a video stream is open.
/// Opens the video stream with the supplied settings. Capture Timeout Milliseconds has to be 0 or greater /// </summary>
/// </summary> public void CloseVideoStream() {
/// <param name="settings">The settings.</param> lock(SyncRoot) {
/// <param name="onDataCallback">The on data callback.</param> if(this.IsBusy == false) {
/// <param name="onExitCallback">The on exit callback.</param> return;
/// <exception cref="InvalidOperationException">Cannot use camera module because it is currently busy.</exception> }
/// <exception cref="ArgumentException">CaptureTimeoutMilliseconds</exception> }
public void OpenVideoStream(CameraVideoSettings settings, Action<byte[]> onDataCallback, Action onExitCallback)
{ if(_videoTokenSource.IsCancellationRequested == false) {
if (Instance.IsBusy) _videoTokenSource.Cancel();
throw new InvalidOperationException("Cannot use camera module because it is currently busy."); _videoStreamTask.Wait();
}
if (settings.CaptureTimeoutMilliseconds < 0)
throw new ArgumentException($"{nameof(settings.CaptureTimeoutMilliseconds)} needs to be greater than or equal to 0"); _videoTokenSource = new CancellationTokenSource();
}
try
{ private static async Task VideoWorkerDoWork(CameraVideoSettings settings, Action<Byte[]> onDataCallback, Action onExitCallback) {
OperationDone.Reset(); try {
_videoStreamTask = Task.Factory.StartNew(() => VideoWorkerDoWork(settings, onDataCallback, onExitCallback), _videoTokenSource.Token); await ProcessRunner.RunProcessAsync(
} settings.CommandName,
catch settings.CreateProcessArguments(),
{ (data, proc) => onDataCallback?.Invoke(data),
OperationDone.Set(); null,
throw; true,
} _videoTokenSource.Token);
}
onExitCallback?.Invoke();
/// <summary> } catch {
/// Closes the video stream of a video stream is open. // swallow
/// </summary> } finally {
public void CloseVideoStream() Instance.CloseVideoStream();
{ OperationDone.Set();
lock (SyncRoot) }
{ }
if (IsBusy == false) #endregion
return; }
}
if (_videoTokenSource.IsCancellationRequested == false)
{
_videoTokenSource.Cancel();
_videoStreamTask.Wait();
}
_videoTokenSource = new CancellationTokenSource();
}
private static async Task VideoWorkerDoWork(
CameraVideoSettings settings,
Action<byte[]> onDataCallback,
Action onExitCallback)
{
try
{
await ProcessRunner.RunProcessAsync(
settings.CommandName,
settings.CreateProcessArguments(),
(data, proc) => onDataCallback?.Invoke(data),
null,
true,
_videoTokenSource.Token);
onExitCallback?.Invoke();
}
catch
{
// swallow
}
finally
{
Instance.CloseVideoStream();
OperationDone.Set();
}
}
#endregion
}
} }

View File

@ -1,82 +1,86 @@
namespace Unosquare.RaspberryIO.Camera using Unosquare.Swan;
{ using System;
using Swan; using System.Globalization;
using System.Globalization;
namespace Unosquare.RaspberryIO.Camera {
/// <summary>
/// Defines the Raspberry Pi camera's sensor ROI (Region of Interest)
/// </summary>
public struct CameraRect {
/// <summary> /// <summary>
/// Defines the Raspberry Pi camera's sensor ROI (Region of Interest) /// The default ROI which is the entire area.
/// </summary> /// </summary>
public struct CameraRect public static readonly CameraRect Default = new CameraRect { X = 0M, Y = 0M, W = 1.0M, H = 1.0M };
{
/// <summary> /// <summary>
/// The default ROI which is the entire area. /// Gets or sets the x in relative coordinates. (0.0 to 1.0)
/// </summary> /// </summary>
public static readonly CameraRect Default = new CameraRect { X = 0M, Y = 0M, W = 1.0M, H = 1.0M }; /// <value>
/// The x.
/// <summary> /// </value>
/// Gets or sets the x in relative coordinates. (0.0 to 1.0) public Decimal X {
/// </summary> get; set;
/// <value> }
/// The x.
/// </value> /// <summary>
public decimal X { get; set; } /// Gets or sets the y location in relative coordinates. (0.0 to 1.0)
/// </summary>
/// <summary> /// <value>
/// Gets or sets the y location in relative coordinates. (0.0 to 1.0) /// The y.
/// </summary> /// </value>
/// <value> public Decimal Y {
/// The y. get; set;
/// </value> }
public decimal Y { get; set; }
/// <summary>
/// <summary> /// Gets or sets the width in relative coordinates. (0.0 to 1.0)
/// Gets or sets the width in relative coordinates. (0.0 to 1.0) /// </summary>
/// </summary> /// <value>
/// <value> /// The w.
/// The w. /// </value>
/// </value> public Decimal W {
public decimal W { get; set; } get; set;
}
/// <summary>
/// Gets or sets the height in relative coordinates. (0.0 to 1.0) /// <summary>
/// </summary> /// Gets or sets the height in relative coordinates. (0.0 to 1.0)
/// <value> /// </summary>
/// The h. /// <value>
/// </value> /// The h.
public decimal H { get; set; } /// </value>
public Decimal H {
/// <summary> get; set;
/// Gets a value indicating whether this instance is equal to the default (The entire area). }
/// </summary>
/// <value> /// <summary>
/// <c>true</c> if this instance is default; otherwise, <c>false</c>. /// Gets a value indicating whether this instance is equal to the default (The entire area).
/// </value> /// </summary>
public bool IsDefault /// <value>
{ /// <c>true</c> if this instance is default; otherwise, <c>false</c>.
get /// </value>
{ public Boolean IsDefault {
Clamp(); get {
return X == Default.X && Y == Default.Y && W == Default.W && H == Default.H; this.Clamp();
} return this.X == Default.X && this.Y == Default.Y && this.W == Default.W && this.H == Default.H;
} }
}
/// <summary>
/// Clamps the members of this ROI to their minimum and maximum values /// <summary>
/// </summary> /// Clamps the members of this ROI to their minimum and maximum values
public void Clamp() /// </summary>
{ public void Clamp() {
X = X.Clamp(0M, 1M); this.X = this.X.Clamp(0M, 1M);
Y = Y.Clamp(0M, 1M); this.Y = this.Y.Clamp(0M, 1M);
W = W.Clamp(0M, 1M - X); this.W = this.W.Clamp(0M, 1M - this.X);
H = H.Clamp(0M, 1M - Y); this.H = this.H.Clamp(0M, 1M - this.Y);
} }
/// <summary> /// <summary>
/// Returns a <see cref="string" /> that represents this instance. /// Returns a <see cref="String" /> that represents this instance.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A <see cref="string" /> that represents this instance. /// A <see cref="String" /> that represents this instance.
/// </returns> /// </returns>
public override string ToString() => $"{X.ToString(CultureInfo.InvariantCulture)},{Y.ToString(CultureInfo.InvariantCulture)},{W.ToString(CultureInfo.InvariantCulture)},{H.ToString(CultureInfo.InvariantCulture)}"; public override String ToString() => $"{this.X.ToString(CultureInfo.InvariantCulture)},{this.Y.ToString(CultureInfo.InvariantCulture)},{this.W.ToString(CultureInfo.InvariantCulture)},{this.H.ToString(CultureInfo.InvariantCulture)}";
} }
} }

View File

@ -1,340 +1,361 @@
namespace Unosquare.RaspberryIO.Camera using Unosquare.Swan;
{ using System.Globalization;
using Swan; using System.Text;
using System.Globalization; using System;
using System.Text;
namespace Unosquare.RaspberryIO.Camera {
/// <summary>
/// A base class to implement raspistill and raspivid wrappers
/// Full documentation available at
/// https://www.raspberrypi.org/documentation/raspbian/applications/camera.md
/// </summary>
public abstract class CameraSettingsBase {
/// <summary> /// <summary>
/// A base class to implement raspistill and raspivid wrappers /// The Invariant Culture shorthand
/// Full documentation available at
/// https://www.raspberrypi.org/documentation/raspbian/applications/camera.md
/// </summary> /// </summary>
public abstract class CameraSettingsBase protected static readonly CultureInfo Ci = CultureInfo.InvariantCulture;
{
/// <summary> #region Capture Settings
/// The Invariant Culture shorthand
/// </summary> /// <summary>
protected static readonly CultureInfo Ci = CultureInfo.InvariantCulture; /// Gets or sets the timeout milliseconds.
/// Default value is 5000
#region Capture Settings /// Recommended value is at least 300 in order to let the light collectors open
/// </summary>
/// <summary> public Int32 CaptureTimeoutMilliseconds { get; set; } = 5000;
/// Gets or sets the timeout milliseconds.
/// Default value is 5000 /// <summary>
/// Recommended value is at least 300 in order to let the light collectors open /// Gets or sets a value indicating whether or not to show a preview window on the screen
/// </summary> /// </summary>
public int CaptureTimeoutMilliseconds { get; set; } = 5000; public Boolean CaptureDisplayPreview { get; set; } = false;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether or not to show a preview window on the screen /// Gets or sets a value indicating whether a preview window is shown in full screen mode if enabled
/// </summary> /// </summary>
public bool CaptureDisplayPreview { get; set; } = false; public Boolean CaptureDisplayPreviewInFullScreen { get; set; } = true;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether a preview window is shown in full screen mode if enabled /// Gets or sets a value indicating whether video stabilization should be enabled.
/// </summary> /// </summary>
public bool CaptureDisplayPreviewInFullScreen { get; set; } = true; public Boolean CaptureVideoStabilizationEnabled { get; set; } = false;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether video stabilization should be enabled. /// Gets or sets the display preview opacity only if the display preview property is enabled.
/// </summary> /// </summary>
public bool CaptureVideoStabilizationEnabled { get; set; } = false; public Byte CaptureDisplayPreviewOpacity { get; set; } = 255;
/// <summary> /// <summary>
/// Gets or sets the display preview opacity only if the display preview property is enabled. /// Gets or sets the capture sensor region of interest in relative coordinates.
/// </summary> /// </summary>
public byte CaptureDisplayPreviewOpacity { get; set; } = 255; public CameraRect CaptureSensorRoi { get; set; } = CameraRect.Default;
/// <summary> /// <summary>
/// Gets or sets the capture sensor region of interest in relative coordinates. /// Gets or sets the capture shutter speed in microseconds.
/// </summary> /// Default -1, Range 0 to 6000000 (equivalent to 6 seconds)
public CameraRect CaptureSensorRoi { get; set; } = CameraRect.Default; /// </summary>
public Int32 CaptureShutterSpeedMicroseconds { get; set; } = -1;
/// <summary>
/// Gets or sets the capture shutter speed in microseconds. /// <summary>
/// Default -1, Range 0 to 6000000 (equivalent to 6 seconds) /// Gets or sets the exposure mode.
/// </summary> /// </summary>
public int CaptureShutterSpeedMicroseconds { get; set; } = -1; public CameraExposureMode CaptureExposure { get; set; } = CameraExposureMode.Auto;
/// <summary> /// <summary>
/// Gets or sets the exposure mode. /// Gets or sets the picture EV compensation. Default is 0, Range is -10 to 10
/// </summary> /// Camera exposure compensation is commonly stated in terms of EV units;
public CameraExposureMode CaptureExposure { get; set; } = CameraExposureMode.Auto; /// 1 EV is equal to one exposure step (or stop), corresponding to a doubling of exposure.
/// Exposure can be adjusted by changing either the lens f-number or the exposure time;
/// <summary> /// which one is changed usually depends on the camera's exposure mode.
/// Gets or sets the picture EV compensation. Default is 0, Range is -10 to 10 /// </summary>
/// Camera exposure compensation is commonly stated in terms of EV units; public Int32 CaptureExposureCompensation { get; set; } = 0;
/// 1 EV is equal to one exposure step (or stop), corresponding to a doubling of exposure.
/// Exposure can be adjusted by changing either the lens f-number or the exposure time; /// <summary>
/// which one is changed usually depends on the camera's exposure mode. /// Gets or sets the capture metering mode.
/// </summary> /// </summary>
public int CaptureExposureCompensation { get; set; } = 0; public CameraMeteringMode CaptureMeteringMode { get; set; } = CameraMeteringMode.Average;
/// <summary> /// <summary>
/// Gets or sets the capture metering mode. /// Gets or sets the automatic white balance mode. By default it is set to Auto
/// </summary> /// </summary>
public CameraMeteringMode CaptureMeteringMode { get; set; } = CameraMeteringMode.Average; public CameraWhiteBalanceMode CaptureWhiteBalanceControl { get; set; } = CameraWhiteBalanceMode.Auto;
/// <summary> /// <summary>
/// Gets or sets the automatic white balance mode. By default it is set to Auto /// Gets or sets the capture white balance gain on the blue channel. Example: 1.25
/// </summary> /// Only takes effect if White balance control is set to off.
public CameraWhiteBalanceMode CaptureWhiteBalanceControl { get; set; } = CameraWhiteBalanceMode.Auto; /// Default is 0
/// </summary>
/// <summary> public Decimal CaptureWhiteBalanceGainBlue { get; set; } = 0M;
/// Gets or sets the capture white balance gain on the blue channel. Example: 1.25
/// Only takes effect if White balance control is set to off. /// <summary>
/// Default is 0 /// Gets or sets the capture white balance gain on the red channel. Example: 1.75
/// </summary> /// Only takes effect if White balance control is set to off.
public decimal CaptureWhiteBalanceGainBlue { get; set; } = 0M; /// Default is 0
/// </summary>
/// <summary> public Decimal CaptureWhiteBalanceGainRed { get; set; } = 0M;
/// Gets or sets the capture white balance gain on the red channel. Example: 1.75
/// Only takes effect if White balance control is set to off. /// <summary>
/// Default is 0 /// Gets or sets the dynamic range compensation.
/// </summary> /// DRC changes the images by increasing the range of dark areas, and decreasing the brighter areas. This can improve the image in low light areas.
public decimal CaptureWhiteBalanceGainRed { get; set; } = 0M; /// </summary>
public CameraDynamicRangeCompensation CaptureDynamicRangeCompensation {
/// <summary> get; set;
/// Gets or sets the dynamic range compensation. } =
/// DRC changes the images by increasing the range of dark areas, and decreasing the brighter areas. This can improve the image in low light areas. CameraDynamicRangeCompensation.Off;
/// </summary>
public CameraDynamicRangeCompensation CaptureDynamicRangeCompensation { get; set; } = #endregion
CameraDynamicRangeCompensation.Off;
#region Image Properties
#endregion
/// <summary>
#region Image Properties /// Gets or sets the width of the picture to take.
/// Less than or equal to 0 in either width or height means maximum resolution available.
/// <summary> /// </summary>
/// Gets or sets the width of the picture to take. public Int32 CaptureWidth { get; set; } = 640;
/// Less than or equal to 0 in either width or height means maximum resolution available.
/// </summary> /// <summary>
public int CaptureWidth { get; set; } = 640; /// Gets or sets the height of the picture to take.
/// Less than or equal to 0 in either width or height means maximum resolution available.
/// <summary> /// </summary>
/// Gets or sets the height of the picture to take. public Int32 CaptureHeight { get; set; } = 480;
/// Less than or equal to 0 in either width or height means maximum resolution available.
/// </summary> /// <summary>
public int CaptureHeight { get; set; } = 480; /// Gets or sets the picture sharpness. Default is 0, Range form -100 to 100
/// </summary>
/// <summary> public Int32 ImageSharpness { get; set; } = 0;
/// Gets or sets the picture sharpness. Default is 0, Range form -100 to 100
/// </summary> /// <summary>
public int ImageSharpness { get; set; } = 0; /// Gets or sets the picture contrast. Default is 0, Range form -100 to 100
/// </summary>
/// <summary> public Int32 ImageContrast { get; set; } = 0;
/// Gets or sets the picture contrast. Default is 0, Range form -100 to 100
/// </summary> /// <summary>
public int ImageContrast { get; set; } = 0; /// Gets or sets the picture brightness. Default is 50, Range form 0 to 100
/// </summary>
/// <summary> public Int32 ImageBrightness { get; set; } = 50; // from 0 to 100
/// Gets or sets the picture brightness. Default is 50, Range form 0 to 100
/// </summary> /// <summary>
public int ImageBrightness { get; set; } = 50; // from 0 to 100 /// Gets or sets the picture saturation. Default is 0, Range form -100 to 100
/// </summary>
/// <summary> public Int32 ImageSaturation { get; set; } = 0;
/// Gets or sets the picture saturation. Default is 0, Range form -100 to 100
/// </summary> /// <summary>
public int ImageSaturation { get; set; } = 0; /// Gets or sets the picture ISO. Default is -1 Range is 100 to 800
/// The higher the value, the more light the sensor absorbs
/// <summary> /// </summary>
/// Gets or sets the picture ISO. Default is -1 Range is 100 to 800 public Int32 ImageIso { get; set; } = -1;
/// The higher the value, the more light the sensor absorbs
/// </summary> /// <summary>
public int ImageIso { get; set; } = -1; /// Gets or sets the image capture effect to be applied.
/// </summary>
/// <summary> public CameraImageEffect ImageEffect { get; set; } = CameraImageEffect.None;
/// Gets or sets the image capture effect to be applied.
/// </summary> /// <summary>
public CameraImageEffect ImageEffect { get; set; } = CameraImageEffect.None; /// Gets or sets the color effect U coordinates.
/// Default is -1, Range is 0 to 255
/// <summary> /// 128:128 should be effectively a monochrome image.
/// Gets or sets the color effect U coordinates. /// </summary>
/// Default is -1, Range is 0 to 255 public Int32 ImageColorEffectU { get; set; } = -1; // 0 to 255
/// 128:128 should be effectively a monochrome image.
/// </summary> /// <summary>
public int ImageColorEffectU { get; set; } = -1; // 0 to 255 /// Gets or sets the color effect V coordinates.
/// Default is -1, Range is 0 to 255
/// <summary> /// 128:128 should be effectively a monochrome image.
/// Gets or sets the color effect V coordinates. /// </summary>
/// Default is -1, Range is 0 to 255 public Int32 ImageColorEffectV { get; set; } = -1; // 0 to 255
/// 128:128 should be effectively a monochrome image.
/// </summary> /// <summary>
public int ImageColorEffectV { get; set; } = -1; // 0 to 255 /// Gets or sets the image rotation. Default is no rotation
/// </summary>
/// <summary> public CameraImageRotation ImageRotation { get; set; } = CameraImageRotation.None;
/// Gets or sets the image rotation. Default is no rotation
/// </summary> /// <summary>
public CameraImageRotation ImageRotation { get; set; } = CameraImageRotation.None; /// Gets or sets a value indicating whether the image should be flipped horizontally.
/// </summary>
/// <summary> public Boolean ImageFlipHorizontally {
/// Gets or sets a value indicating whether the image should be flipped horizontally. get; set;
/// </summary> }
public bool ImageFlipHorizontally { get; set; }
/// <summary>
/// <summary> /// Gets or sets a value indicating whether the image should be flipped vertically.
/// Gets or sets a value indicating whether the image should be flipped vertically. /// </summary>
/// </summary> public Boolean ImageFlipVertically {
public bool ImageFlipVertically { get; set; } get; set;
}
/// <summary>
/// Gets or sets the image annotations using a bitmask (or flags) notation. /// <summary>
/// Apply a bitwise OR to the enumeration to include multiple annotations /// Gets or sets the image annotations using a bitmask (or flags) notation.
/// </summary> /// Apply a bitwise OR to the enumeration to include multiple annotations
public CameraAnnotation ImageAnnotations { get; set; } = CameraAnnotation.None; /// </summary>
public CameraAnnotation ImageAnnotations { get; set; } = CameraAnnotation.None;
/// <summary>
/// Gets or sets the image annotations text. /// <summary>
/// Text may include date/time placeholders by using the '%' character, as used by strftime. /// Gets or sets the image annotations text.
/// Example: ABC %Y-%m-%d %X will output ABC 2015-10-28 20:09:33 /// Text may include date/time placeholders by using the '%' character, as used by strftime.
/// </summary> /// Example: ABC %Y-%m-%d %X will output ABC 2015-10-28 20:09:33
public string ImageAnnotationsText { get; set; } = string.Empty; /// </summary>
public String ImageAnnotationsText { get; set; } = String.Empty;
/// <summary>
/// Gets or sets the font size of the text annotations /// <summary>
/// Default is -1, range is 6 to 160 /// Gets or sets the font size of the text annotations
/// </summary> /// Default is -1, range is 6 to 160
public int ImageAnnotationFontSize { get; set; } = -1; /// </summary>
public Int32 ImageAnnotationFontSize { get; set; } = -1;
/// <summary>
/// Gets or sets the color of the text annotations. /// <summary>
/// </summary> /// Gets or sets the color of the text annotations.
/// <value> /// </summary>
/// The color of the image annotation font. /// <value>
/// </value> /// The color of the image annotation font.
public CameraColor ImageAnnotationFontColor { get; set; } = null; /// </value>
public CameraColor ImageAnnotationFontColor { get; set; } = null;
/// <summary>
/// Gets or sets the background color for text annotations. /// <summary>
/// </summary> /// Gets or sets the background color for text annotations.
/// <value> /// </summary>
/// The image annotation background. /// <value>
/// </value> /// The image annotation background.
public CameraColor ImageAnnotationBackground { get; set; } = null; /// </value>
public CameraColor ImageAnnotationBackground { get; set; } = null;
#endregion
#endregion
#region Interface
#region Interface
/// <summary>
/// Gets the command file executable. /// <summary>
/// </summary> /// Gets the command file executable.
public abstract string CommandName { get; } /// </summary>
public abstract String CommandName {
/// <summary> get;
/// Creates the process arguments. }
/// </summary>
/// <returns>The string that represents the process arguments</returns> /// <summary>
public virtual string CreateProcessArguments() /// Creates the process arguments.
{ /// </summary>
var sb = new StringBuilder(); /// <returns>The string that represents the process arguments</returns>
sb.Append("-o -"); // output to standard output as opposed to a file. public virtual String CreateProcessArguments() {
sb.Append($" -t {(CaptureTimeoutMilliseconds < 0 ? "0" : CaptureTimeoutMilliseconds.ToString(Ci))}"); StringBuilder sb = new StringBuilder();
_ = sb.Append("-o -"); // output to standard output as opposed to a file.
// Basic Width and height _ = sb.Append($" -t {(this.CaptureTimeoutMilliseconds < 0 ? "0" : this.CaptureTimeoutMilliseconds.ToString(Ci))}");
if (CaptureWidth > 0 && CaptureHeight > 0)
{ // Basic Width and height
sb.Append($" -w {CaptureWidth.ToString(Ci)}"); if(this.CaptureWidth > 0 && this.CaptureHeight > 0) {
sb.Append($" -h {CaptureHeight.ToString(Ci)}"); _ = sb.Append($" -w {this.CaptureWidth.ToString(Ci)}");
} _ = sb.Append($" -h {this.CaptureHeight.ToString(Ci)}");
}
// Display Preview
if (CaptureDisplayPreview) // Display Preview
{ if(this.CaptureDisplayPreview) {
if (CaptureDisplayPreviewInFullScreen) if(this.CaptureDisplayPreviewInFullScreen) {
sb.Append(" -f"); _ = sb.Append(" -f");
}
if (CaptureDisplayPreviewOpacity != byte.MaxValue)
sb.Append($" -op {CaptureDisplayPreviewOpacity.ToString(Ci)}"); if(this.CaptureDisplayPreviewOpacity != Byte.MaxValue) {
} _ = sb.Append($" -op {this.CaptureDisplayPreviewOpacity.ToString(Ci)}");
else }
{ } else {
sb.Append(" -n"); // no preview _ = sb.Append(" -n"); // no preview
} }
// Picture Settings // Picture Settings
if (ImageSharpness != 0) if(this.ImageSharpness != 0) {
sb.Append($" -sh {ImageSharpness.Clamp(-100, 100).ToString(Ci)}"); _ = sb.Append($" -sh {this.ImageSharpness.Clamp(-100, 100).ToString(Ci)}");
}
if (ImageContrast != 0)
sb.Append($" -co {ImageContrast.Clamp(-100, 100).ToString(Ci)}"); if(this.ImageContrast != 0) {
_ = sb.Append($" -co {this.ImageContrast.Clamp(-100, 100).ToString(Ci)}");
if (ImageBrightness != 50) }
sb.Append($" -br {ImageBrightness.Clamp(0, 100).ToString(Ci)}");
if(this.ImageBrightness != 50) {
if (ImageSaturation != 0) _ = sb.Append($" -br {this.ImageBrightness.Clamp(0, 100).ToString(Ci)}");
sb.Append($" -sa {ImageSaturation.Clamp(-100, 100).ToString(Ci)}"); }
if (ImageIso >= 100) if(this.ImageSaturation != 0) {
sb.Append($" -ISO {ImageIso.Clamp(100, 800).ToString(Ci)}"); _ = sb.Append($" -sa {this.ImageSaturation.Clamp(-100, 100).ToString(Ci)}");
}
if (CaptureVideoStabilizationEnabled)
sb.Append(" -vs"); if(this.ImageIso >= 100) {
_ = sb.Append($" -ISO {this.ImageIso.Clamp(100, 800).ToString(Ci)}");
if (CaptureExposureCompensation != 0) }
sb.Append($" -ev {CaptureExposureCompensation.Clamp(-10, 10).ToString(Ci)}");
if(this.CaptureVideoStabilizationEnabled) {
if (CaptureExposure != CameraExposureMode.Auto) _ = sb.Append(" -vs");
sb.Append($" -ex {CaptureExposure.ToString().ToLowerInvariant()}"); }
if (CaptureWhiteBalanceControl != CameraWhiteBalanceMode.Auto) if(this.CaptureExposureCompensation != 0) {
sb.Append($" -awb {CaptureWhiteBalanceControl.ToString().ToLowerInvariant()}"); _ = sb.Append($" -ev {this.CaptureExposureCompensation.Clamp(-10, 10).ToString(Ci)}");
}
if (ImageEffect != CameraImageEffect.None)
sb.Append($" -ifx {ImageEffect.ToString().ToLowerInvariant()}"); if(this.CaptureExposure != CameraExposureMode.Auto) {
_ = sb.Append($" -ex {this.CaptureExposure.ToString().ToLowerInvariant()}");
if (ImageColorEffectU >= 0 && ImageColorEffectV >= 0) }
{
sb.Append( if(this.CaptureWhiteBalanceControl != CameraWhiteBalanceMode.Auto) {
$" -cfx {ImageColorEffectU.Clamp(0, 255).ToString(Ci)}:{ImageColorEffectV.Clamp(0, 255).ToString(Ci)}"); _ = sb.Append($" -awb {this.CaptureWhiteBalanceControl.ToString().ToLowerInvariant()}");
} }
if (CaptureMeteringMode != CameraMeteringMode.Average) if(this.ImageEffect != CameraImageEffect.None) {
sb.Append($" -mm {CaptureMeteringMode.ToString().ToLowerInvariant()}"); _ = sb.Append($" -ifx {this.ImageEffect.ToString().ToLowerInvariant()}");
}
if (ImageRotation != CameraImageRotation.None)
sb.Append($" -rot {((int)ImageRotation).ToString(Ci)}"); if(this.ImageColorEffectU >= 0 && this.ImageColorEffectV >= 0) {
_ = sb.Append(
if (ImageFlipHorizontally) $" -cfx {this.ImageColorEffectU.Clamp(0, 255).ToString(Ci)}:{this.ImageColorEffectV.Clamp(0, 255).ToString(Ci)}");
sb.Append(" -hf"); }
if (ImageFlipVertically) if(this.CaptureMeteringMode != CameraMeteringMode.Average) {
sb.Append(" -vf"); _ = sb.Append($" -mm {this.CaptureMeteringMode.ToString().ToLowerInvariant()}");
}
if (CaptureSensorRoi.IsDefault == false)
sb.Append($" -roi {CaptureSensorRoi}"); if(this.ImageRotation != CameraImageRotation.None) {
_ = sb.Append($" -rot {((Int32)this.ImageRotation).ToString(Ci)}");
if (CaptureShutterSpeedMicroseconds > 0) }
sb.Append($" -ss {CaptureShutterSpeedMicroseconds.Clamp(0, 6000000).ToString(Ci)}");
if(this.ImageFlipHorizontally) {
if (CaptureDynamicRangeCompensation != CameraDynamicRangeCompensation.Off) _ = sb.Append(" -hf");
sb.Append($" -drc {CaptureDynamicRangeCompensation.ToString().ToLowerInvariant()}"); }
if (CaptureWhiteBalanceControl == CameraWhiteBalanceMode.Off && if(this.ImageFlipVertically) {
(CaptureWhiteBalanceGainBlue != 0M || CaptureWhiteBalanceGainRed != 0M)) _ = sb.Append(" -vf");
sb.Append($" -awbg {CaptureWhiteBalanceGainBlue.ToString(Ci)},{CaptureWhiteBalanceGainRed.ToString(Ci)}"); }
if (ImageAnnotationFontSize > 0) if(this.CaptureSensorRoi.IsDefault == false) {
{ _ = sb.Append($" -roi {this.CaptureSensorRoi}");
sb.Append($" -ae {ImageAnnotationFontSize.Clamp(6, 160).ToString(Ci)}"); }
sb.Append($",{(ImageAnnotationFontColor == null ? "0xff" : ImageAnnotationFontColor.ToYuvHex(true))}");
if(this.CaptureShutterSpeedMicroseconds > 0) {
if (ImageAnnotationBackground != null) _ = sb.Append($" -ss {this.CaptureShutterSpeedMicroseconds.Clamp(0, 6000000).ToString(Ci)}");
{ }
ImageAnnotations |= CameraAnnotation.SolidBackground;
sb.Append($",{ImageAnnotationBackground.ToYuvHex(true)}"); if(this.CaptureDynamicRangeCompensation != CameraDynamicRangeCompensation.Off) {
} _ = sb.Append($" -drc {this.CaptureDynamicRangeCompensation.ToString().ToLowerInvariant()}");
} }
if (ImageAnnotations != CameraAnnotation.None) if(this.CaptureWhiteBalanceControl == CameraWhiteBalanceMode.Off &&
sb.Append($" -a {((int)ImageAnnotations).ToString(Ci)}"); (this.CaptureWhiteBalanceGainBlue != 0M || this.CaptureWhiteBalanceGainRed != 0M)) {
_ = sb.Append($" -awbg {this.CaptureWhiteBalanceGainBlue.ToString(Ci)},{this.CaptureWhiteBalanceGainRed.ToString(Ci)}");
if (string.IsNullOrWhiteSpace(ImageAnnotationsText) == false) }
sb.Append($" -a \"{ImageAnnotationsText.Replace("\"", "'")}\"");
if(this.ImageAnnotationFontSize > 0) {
return sb.ToString(); _ = sb.Append($" -ae {this.ImageAnnotationFontSize.Clamp(6, 160).ToString(Ci)}");
} _ = sb.Append($",{(this.ImageAnnotationFontColor == null ? "0xff" : this.ImageAnnotationFontColor.ToYuvHex(true))}");
#endregion if(this.ImageAnnotationBackground != null) {
} this.ImageAnnotations |= CameraAnnotation.SolidBackground;
_ = sb.Append($",{this.ImageAnnotationBackground.ToYuvHex(true)}");
}
}
if(this.ImageAnnotations != CameraAnnotation.None) {
_ = sb.Append($" -a {((Int32)this.ImageAnnotations).ToString(Ci)}");
}
if(String.IsNullOrWhiteSpace(this.ImageAnnotationsText) == false) {
_ = sb.Append($" -a \"{this.ImageAnnotationsText.Replace("\"", "'")}\"");
}
return sb.ToString();
}
#endregion
}
} }

View File

@ -1,120 +1,121 @@
namespace Unosquare.RaspberryIO.Camera using Unosquare.Swan;
{ using System;
using Swan; using System.Collections.Generic;
using System; using System.Text;
using System.Collections.Generic;
using System.Text; namespace Unosquare.RaspberryIO.Camera {
/// <summary>
/// Defines a wrapper for the raspistill program and its settings (command-line arguments)
/// </summary>
/// <seealso cref="CameraSettingsBase" />
public class CameraStillSettings : CameraSettingsBase {
private Int32 _rotate;
/// <inheritdoc />
public override String CommandName => "raspistill";
/// <summary> /// <summary>
/// Defines a wrapper for the raspistill program and its settings (command-line arguments) /// Gets or sets a value indicating whether the preview window (if enabled) uses native capture resolution
/// This may slow down preview FPS
/// </summary> /// </summary>
/// <seealso cref="CameraSettingsBase" /> public Boolean CaptureDisplayPreviewAtResolution { get; set; } = false;
public class CameraStillSettings : CameraSettingsBase
{ /// <summary>
private int _rotate; /// Gets or sets the encoding format the hardware will use for the output.
/// </summary>
/// <inheritdoc /> public CameraImageEncodingFormat CaptureEncoding { get; set; } = CameraImageEncodingFormat.Jpg;
public override string CommandName => "raspistill";
/// <summary>
/// <summary> /// Gets or sets the quality for JPEG only encoding mode.
/// Gets or sets a value indicating whether the preview window (if enabled) uses native capture resolution /// Value ranges from 0 to 100
/// This may slow down preview FPS /// </summary>
/// </summary> public Int32 CaptureJpegQuality { get; set; } = 90;
public bool CaptureDisplayPreviewAtResolution { get; set; } = false;
/// <summary>
/// <summary> /// Gets or sets a value indicating whether the JPEG encoder should add raw bayer metadata.
/// Gets or sets the encoding format the hardware will use for the output. /// </summary>
/// </summary> public Boolean CaptureJpegIncludeRawBayerMetadata { get; set; } = false;
public CameraImageEncodingFormat CaptureEncoding { get; set; } = CameraImageEncodingFormat.Jpg;
/// <summary>
/// <summary> /// JPEG EXIF data
/// Gets or sets the quality for JPEG only encoding mode. /// Keys and values must be already properly escaped. Otherwise the command will fail.
/// Value ranges from 0 to 100 /// </summary>
/// </summary> public Dictionary<String, String> CaptureJpegExtendedInfo { get; } = new Dictionary<String, String>();
public int CaptureJpegQuality { get; set; } = 90;
/// <summary>
/// <summary> /// Gets or sets a value indicating whether [horizontal flip].
/// Gets or sets a value indicating whether the JPEG encoder should add raw bayer metadata. /// </summary>
/// </summary> /// <value>
public bool CaptureJpegIncludeRawBayerMetadata { get; set; } = false; /// <c>true</c> if [horizontal flip]; otherwise, <c>false</c>.
/// </value>
/// <summary> public Boolean HorizontalFlip { get; set; } = false;
/// JPEG EXIF data
/// Keys and values must be already properly escaped. Otherwise the command will fail. /// <summary>
/// </summary> /// Gets or sets a value indicating whether [vertical flip].
public Dictionary<string, string> CaptureJpegExtendedInfo { get; } = new Dictionary<string, string>(); /// </summary>
/// <value>
/// <summary> /// <c>true</c> if [vertical flip]; otherwise, <c>false</c>.
/// Gets or sets a value indicating whether [horizontal flip]. /// </value>
/// </summary> public Boolean VerticalFlip { get; set; } = false;
/// <value>
/// <c>true</c> if [horizontal flip]; otherwise, <c>false</c>. /// <summary>
/// </value> /// Gets or sets the rotation.
public bool HorizontalFlip { get; set; } = false; /// </summary>
/// <exception cref="ArgumentOutOfRangeException">Valid range 0-359</exception>
/// <summary> public Int32 Rotation {
/// Gets or sets a value indicating whether [vertical flip]. get => this._rotate;
/// </summary> set {
/// <value> if(value < 0 || value > 359) {
/// <c>true</c> if [vertical flip]; otherwise, <c>false</c>. throw new ArgumentOutOfRangeException(nameof(value), "Valid range 0-359");
/// </value> }
public bool VerticalFlip { get; set; } = false;
this._rotate = value;
/// <summary> }
/// Gets or sets the rotation. }
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Valid range 0-359</exception> /// <inheritdoc />
public int Rotation public override String CreateProcessArguments() {
{ StringBuilder sb = new StringBuilder(base.CreateProcessArguments());
get => _rotate; _ = sb.Append($" -e {this.CaptureEncoding.ToString().ToLowerInvariant()}");
set
{ // JPEG Encoder specific arguments
if (value < 0 || value > 359) if(this.CaptureEncoding == CameraImageEncodingFormat.Jpg) {
{ _ = sb.Append($" -q {this.CaptureJpegQuality.Clamp(0, 100).ToString(Ci)}");
throw new ArgumentOutOfRangeException(nameof(value), "Valid range 0-359");
} if(this.CaptureJpegIncludeRawBayerMetadata) {
_ = sb.Append(" -r");
_rotate = value; }
}
} // JPEG EXIF data
if(this.CaptureJpegExtendedInfo.Count > 0) {
/// <inheritdoc /> foreach(KeyValuePair<String, String> kvp in this.CaptureJpegExtendedInfo) {
public override string CreateProcessArguments() if(String.IsNullOrWhiteSpace(kvp.Key) || String.IsNullOrWhiteSpace(kvp.Value)) {
{ continue;
var sb = new StringBuilder(base.CreateProcessArguments()); }
sb.Append($" -e {CaptureEncoding.ToString().ToLowerInvariant()}");
_ = sb.Append($" -x \"{kvp.Key.Replace("\"", "'")}={kvp.Value.Replace("\"", "'")}\"");
// JPEG Encoder specific arguments }
if (CaptureEncoding == CameraImageEncodingFormat.Jpg) }
{ }
sb.Append($" -q {CaptureJpegQuality.Clamp(0, 100).ToString(Ci)}");
// Display preview settings
if (CaptureJpegIncludeRawBayerMetadata) if(this.CaptureDisplayPreview && this.CaptureDisplayPreviewAtResolution) {
sb.Append(" -r"); _ = sb.Append(" -fp");
}
// JPEG EXIF data
if (CaptureJpegExtendedInfo.Count > 0) if(this.Rotation != 0) {
{ _ = sb.Append($" -rot {this.Rotation}");
foreach (var kvp in CaptureJpegExtendedInfo) }
{
if (string.IsNullOrWhiteSpace(kvp.Key) || string.IsNullOrWhiteSpace(kvp.Value)) if(this.HorizontalFlip) {
continue; _ = sb.Append(" -hf");
}
sb.Append($" -x \"{kvp.Key.Replace("\"", "'")}={kvp.Value.Replace("\"", "'")}\"");
} if(this.VerticalFlip) {
} _ = sb.Append(" -vf");
} }
// Display preview settings return sb.ToString();
if (CaptureDisplayPreview && CaptureDisplayPreviewAtResolution) sb.Append(" -fp"); }
}
if (Rotation != 0) sb.Append($" -rot {Rotation}");
if (HorizontalFlip) sb.Append(" -hf");
if (VerticalFlip) sb.Append(" -vf");
return sb.ToString();
}
}
} }

View File

@ -1,94 +1,98 @@
namespace Unosquare.RaspberryIO.Camera using Unosquare.Swan;
{ using System;
using Swan; using System.Text;
using System.Text;
namespace Unosquare.RaspberryIO.Camera {
/// <summary>
/// Represents the raspivid camera settings for video capture functionality
/// </summary>
/// <seealso cref="CameraSettingsBase" />
public class CameraVideoSettings : CameraSettingsBase {
/// <inheritdoc />
public override String CommandName => "raspivid";
/// <summary> /// <summary>
/// Represents the raspivid camera settings for video capture functionality /// Use bits per second, so 10Mbits/s would be -b 10000000. For H264, 1080p30 a high quality bitrate would be 15Mbits/s or more.
/// Maximum bitrate is 25Mbits/s (-b 25000000), but much over 17Mbits/s won't show noticeable improvement at 1080p30.
/// Default -1
/// </summary> /// </summary>
/// <seealso cref="CameraSettingsBase" /> public Int32 CaptureBitrate { get; set; } = -1;
public class CameraVideoSettings : CameraSettingsBase
{ /// <summary>
/// <inheritdoc /> /// Gets or sets the framerate.
public override string CommandName => "raspivid"; /// Default 25, range 2 to 30
/// </summary>
/// <summary> public Int32 CaptureFramerate { get; set; } = 25;
/// Use bits per second, so 10Mbits/s would be -b 10000000. For H264, 1080p30 a high quality bitrate would be 15Mbits/s or more.
/// Maximum bitrate is 25Mbits/s (-b 25000000), but much over 17Mbits/s won't show noticeable improvement at 1080p30. /// <summary>
/// Default -1 /// Sets the intra refresh period (GoP) rate for the recorded video. H264 video uses a complete frame (I-frame) every intra
/// </summary> /// refresh period, from which subsequent frames are based. This option specifies the number of frames between each I-frame.
public int CaptureBitrate { get; set; } = -1; /// Larger numbers here will reduce the size of the resulting video, and smaller numbers make the stream less error-prone.
/// </summary>
/// <summary> public Int32 CaptureKeyframeRate { get; set; } = 25;
/// Gets or sets the framerate.
/// Default 25, range 2 to 30 /// <summary>
/// </summary> /// Sets the initial quantisation parameter for the stream. Varies from approximately 10 to 40, and will greatly affect
public int CaptureFramerate { get; set; } = 25; /// the quality of the recording. Higher values reduce quality and decrease file size. Combine this setting with a
/// bitrate of 0 to set a completely variable bitrate.
/// <summary> /// </summary>
/// Sets the intra refresh period (GoP) rate for the recorded video. H264 video uses a complete frame (I-frame) every intra public Int32 CaptureQuantisation { get; set; } = 23;
/// refresh period, from which subsequent frames are based. This option specifies the number of frames between each I-frame.
/// Larger numbers here will reduce the size of the resulting video, and smaller numbers make the stream less error-prone. /// <summary>
/// </summary> /// Gets or sets the profile.
public int CaptureKeyframeRate { get; set; } = 25; /// Sets the H264 profile to be used for the encoding.
/// Default is Main mode
/// <summary> /// </summary>
/// Sets the initial quantisation parameter for the stream. Varies from approximately 10 to 40, and will greatly affect public CameraH264Profile CaptureProfile { get; set; } = CameraH264Profile.Main;
/// the quality of the recording. Higher values reduce quality and decrease file size. Combine this setting with a
/// bitrate of 0 to set a completely variable bitrate. /// <summary>
/// </summary> /// Forces the stream to include PPS and SPS headers on every I-frame. Needed for certain streaming cases
public int CaptureQuantisation { get; set; } = 23; /// e.g. Apple HLS. These headers are small, so don't greatly increase the file size.
/// </summary>
/// <summary> /// <value>
/// Gets or sets the profile. /// <c>true</c> if [interleave headers]; otherwise, <c>false</c>.
/// Sets the H264 profile to be used for the encoding. /// </value>
/// Default is Main mode public Boolean CaptureInterleaveHeaders { get; set; } = true;
/// </summary>
public CameraH264Profile CaptureProfile { get; set; } = CameraH264Profile.Main; /// <summary>
/// Switch on an option to display the preview after compression. This will show any compression artefacts in the preview window. In normal operation,
/// <summary> /// the preview will show the camera output prior to being compressed. This option is not guaranteed to work in future releases.
/// Forces the stream to include PPS and SPS headers on every I-frame. Needed for certain streaming cases /// </summary>
/// e.g. Apple HLS. These headers are small, so don't greatly increase the file size. /// <value>
/// </summary> /// <c>true</c> if [capture display preview encoded]; otherwise, <c>false</c>.
/// <value> /// </value>
/// <c>true</c> if [interleave headers]; otherwise, <c>false</c>. public Boolean CaptureDisplayPreviewEncoded { get; set; } = false;
/// </value>
public bool CaptureInterleaveHeaders { get; set; } = true; /// <inheritdoc />
public override String CreateProcessArguments() {
/// <summary> StringBuilder sb = new StringBuilder(base.CreateProcessArguments());
/// Switch on an option to display the preview after compression. This will show any compression artefacts in the preview window. In normal operation,
/// the preview will show the camera output prior to being compressed. This option is not guaranteed to work in future releases. _ = sb.Append($" -pf {this.CaptureProfile.ToString().ToLowerInvariant()}");
/// </summary> if(this.CaptureBitrate < 0) {
/// <value> _ = sb.Append($" -b {this.CaptureBitrate.Clamp(0, 25000000).ToString(Ci)}");
/// <c>true</c> if [capture display preview encoded]; otherwise, <c>false</c>. }
/// </value>
public bool CaptureDisplayPreviewEncoded { get; set; } = false; if(this.CaptureFramerate >= 2) {
_ = sb.Append($" -fps {this.CaptureFramerate.Clamp(2, 30).ToString(Ci)}");
/// <inheritdoc /> }
public override string CreateProcessArguments()
{ if(this.CaptureDisplayPreview && this.CaptureDisplayPreviewEncoded) {
var sb = new StringBuilder(base.CreateProcessArguments()); _ = sb.Append(" -e");
}
sb.Append($" -pf {CaptureProfile.ToString().ToLowerInvariant()}");
if (CaptureBitrate < 0) if(this.CaptureKeyframeRate > 0) {
sb.Append($" -b {CaptureBitrate.Clamp(0, 25000000).ToString(Ci)}"); _ = sb.Append($" -g {this.CaptureKeyframeRate.ToString(Ci)}");
}
if (CaptureFramerate >= 2)
sb.Append($" -fps {CaptureFramerate.Clamp(2, 30).ToString(Ci)}"); if(this.CaptureQuantisation >= 0) {
_ = sb.Append($" -qp {this.CaptureQuantisation.Clamp(0, 40).ToString(Ci)}");
if (CaptureDisplayPreview && CaptureDisplayPreviewEncoded) }
sb.Append(" -e");
if(this.CaptureInterleaveHeaders) {
if (CaptureKeyframeRate > 0) _ = sb.Append(" -ih");
sb.Append($" -g {CaptureKeyframeRate.ToString(Ci)}"); }
if (CaptureQuantisation >= 0) return sb.ToString();
sb.Append($" -qp {CaptureQuantisation.Clamp(0, 40).ToString(Ci)}"); }
}
if (CaptureInterleaveHeaders)
sb.Append(" -ih");
return sb.ToString();
}
}
} }

View File

@ -1,423 +1,413 @@
namespace Unosquare.RaspberryIO.Camera using System;
{
using System; namespace Unosquare.RaspberryIO.Camera {
/// <summary>
/// Defines the available encoding formats for the Raspberry Pi camera module
/// </summary>
public enum CameraImageEncodingFormat {
/// <summary> /// <summary>
/// Defines the available encoding formats for the Raspberry Pi camera module /// The JPG
/// </summary> /// </summary>
public enum CameraImageEncodingFormat Jpg,
{
/// <summary>
/// The JPG
/// </summary>
Jpg,
/// <summary>
/// The BMP
/// </summary>
Bmp,
/// <summary>
/// The GIF
/// </summary>
Gif,
/// <summary>
/// The PNG
/// </summary>
Png,
}
/// <summary> /// <summary>
/// Defines the different exposure modes for the Raspberry Pi's camera module /// The BMP
/// </summary> /// </summary>
public enum CameraExposureMode Bmp,
{
/// <summary>
/// The automatic
/// </summary>
Auto,
/// <summary>
/// The night
/// </summary>
Night,
/// <summary>
/// The night preview
/// </summary>
NightPreview,
/// <summary>
/// The backlight
/// </summary>
Backlight,
/// <summary>
/// The spotlight
/// </summary>
Spotlight,
/// <summary>
/// The sports
/// </summary>
Sports,
/// <summary>
/// The snow
/// </summary>
Snow,
/// <summary>
/// The beach
/// </summary>
Beach,
/// <summary>
/// The very long
/// </summary>
VeryLong,
/// <summary>
/// The fixed FPS
/// </summary>
FixedFps,
/// <summary>
/// The anti shake
/// </summary>
AntiShake,
/// <summary>
/// The fireworks
/// </summary>
Fireworks
}
/// <summary> /// <summary>
/// Defines the different AWB (Auto White Balance) modes for the Raspberry Pi's camera module /// The GIF
/// </summary> /// </summary>
public enum CameraWhiteBalanceMode Gif,
{
/// <summary>
/// No white balance
/// </summary>
Off,
/// <summary>
/// The automatic
/// </summary>
Auto,
/// <summary>
/// The sun
/// </summary>
Sun,
/// <summary>
/// The cloud
/// </summary>
Cloud,
/// <summary>
/// The shade
/// </summary>
Shade,
/// <summary>
/// The tungsten
/// </summary>
Tungsten,
/// <summary>
/// The fluorescent
/// </summary>
Fluorescent,
/// <summary>
/// The incandescent
/// </summary>
Incandescent,
/// <summary>
/// The flash
/// </summary>
Flash,
/// <summary>
/// The horizon
/// </summary>
Horizon
}
/// <summary> /// <summary>
/// Defines the available image effects for the Raspberry Pi's camera module /// The PNG
/// </summary> /// </summary>
public enum CameraImageEffect Png,
{ }
/// <summary>
/// No effect /// <summary>
/// </summary> /// Defines the different exposure modes for the Raspberry Pi's camera module
None, /// </summary>
public enum CameraExposureMode {
/// <summary>
/// The negative
/// </summary>
Negative,
/// <summary>
/// The solarise
/// </summary>
Solarise,
/// <summary>
/// The whiteboard
/// </summary>
Whiteboard,
/// <summary>
/// The blackboard
/// </summary>
Blackboard,
/// <summary>
/// The sketch
/// </summary>
Sketch,
/// <summary>
/// The denoise
/// </summary>
Denoise,
/// <summary>
/// The emboss
/// </summary>
Emboss,
/// <summary>
/// The oil paint
/// </summary>
OilPaint,
/// <summary>
/// The hatch
/// </summary>
Hatch,
/// <summary>
/// Graphite Pen
/// </summary>
GPen,
/// <summary>
/// The pastel
/// </summary>
Pastel,
/// <summary>
/// The water colour
/// </summary>
WaterColour,
/// <summary>
/// The film
/// </summary>
Film,
/// <summary>
/// The blur
/// </summary>
Blur,
/// <summary>
/// The saturation
/// </summary>
Saturation,
/// <summary>
/// The solour swap
/// </summary>
SolourSwap,
/// <summary>
/// The washed out
/// </summary>
WashedOut,
/// <summary>
/// The colour point
/// </summary>
ColourPoint,
/// <summary>
/// The colour balance
/// </summary>
ColourBalance,
/// <summary>
/// The cartoon
/// </summary>
Cartoon
}
/// <summary> /// <summary>
/// Defines the different metering modes for the Raspberry Pi's camera module /// The automatic
/// </summary> /// </summary>
public enum CameraMeteringMode Auto,
{
/// <summary>
/// The average
/// </summary>
Average,
/// <summary>
/// The spot
/// </summary>
Spot,
/// <summary>
/// The backlit
/// </summary>
Backlit,
/// <summary>
/// The matrix
/// </summary>
Matrix,
}
/// <summary> /// <summary>
/// Defines the different image rotation modes for the Raspberry Pi's camera module /// The night
/// </summary> /// </summary>
public enum CameraImageRotation Night,
{
/// <summary>
/// No rerotation
/// </summary>
None = 0,
/// <summary>
/// 90 Degrees
/// </summary>
Degrees90 = 90,
/// <summary>
/// 180 Degrees
/// </summary>
Degrees180 = 180,
/// <summary>
/// 270 degrees
/// </summary>
Degrees270 = 270
}
/// <summary> /// <summary>
/// Defines the different DRC (Dynamic Range Compensation) modes for the Raspberry Pi's camera module /// The night preview
/// Helpful for low light photos
/// </summary> /// </summary>
public enum CameraDynamicRangeCompensation NightPreview,
{
/// <summary>
/// The off setting
/// </summary>
Off,
/// <summary>
/// The low
/// </summary>
Low,
/// <summary>
/// The medium
/// </summary>
Medium,
/// <summary>
/// The high
/// </summary>
High
}
/// <summary> /// <summary>
/// Defines the bit-wise mask flags for the available annotation elements for the Raspberry Pi's camera module /// The backlight
/// </summary> /// </summary>
[Flags] Backlight,
public enum CameraAnnotation
{
/// <summary>
/// The none
/// </summary>
None = 0,
/// <summary>
/// The time
/// </summary>
Time = 4,
/// <summary>
/// The date
/// </summary>
Date = 8,
/// <summary>
/// The shutter settings
/// </summary>
ShutterSettings = 16,
/// <summary>
/// The caf settings
/// </summary>
CafSettings = 32,
/// <summary>
/// The gain settings
/// </summary>
GainSettings = 64,
/// <summary>
/// The lens settings
/// </summary>
LensSettings = 128,
/// <summary>
/// The motion settings
/// </summary>
MotionSettings = 256,
/// <summary>
/// The frame number
/// </summary>
FrameNumber = 512,
/// <summary>
/// The solid background
/// </summary>
SolidBackground = 1024,
}
/// <summary> /// <summary>
/// Defines the different H.264 encoding profiles to be used when capturing video. /// The spotlight
/// </summary> /// </summary>
public enum CameraH264Profile Spotlight,
{
/// <summary> /// <summary>
/// BP: Primarily for lower-cost applications with limited computing resources, /// The sports
/// this profile is used widely in videoconferencing and mobile applications. /// </summary>
/// </summary> Sports,
Baseline,
/// <summary>
/// <summary> /// The snow
/// MP: Originally intended as the mainstream consumer profile for broadcast /// </summary>
/// and storage applications, the importance of this profile faded when the High profile was developed for those applications. Snow,
/// </summary>
Main, /// <summary>
/// The beach
/// <summary> /// </summary>
/// HiP: The primary profile for broadcast and disc storage applications, particularly Beach,
/// for high-definition television applications (this is the profile adopted into HD DVD and Blu-ray Disc, for example).
/// </summary> /// <summary>
High /// The very long
} /// </summary>
VeryLong,
/// <summary>
/// The fixed FPS
/// </summary>
FixedFps,
/// <summary>
/// The anti shake
/// </summary>
AntiShake,
/// <summary>
/// The fireworks
/// </summary>
Fireworks
}
/// <summary>
/// Defines the different AWB (Auto White Balance) modes for the Raspberry Pi's camera module
/// </summary>
public enum CameraWhiteBalanceMode {
/// <summary>
/// No white balance
/// </summary>
Off,
/// <summary>
/// The automatic
/// </summary>
Auto,
/// <summary>
/// The sun
/// </summary>
Sun,
/// <summary>
/// The cloud
/// </summary>
Cloud,
/// <summary>
/// The shade
/// </summary>
Shade,
/// <summary>
/// The tungsten
/// </summary>
Tungsten,
/// <summary>
/// The fluorescent
/// </summary>
Fluorescent,
/// <summary>
/// The incandescent
/// </summary>
Incandescent,
/// <summary>
/// The flash
/// </summary>
Flash,
/// <summary>
/// The horizon
/// </summary>
Horizon
}
/// <summary>
/// Defines the available image effects for the Raspberry Pi's camera module
/// </summary>
public enum CameraImageEffect {
/// <summary>
/// No effect
/// </summary>
None,
/// <summary>
/// The negative
/// </summary>
Negative,
/// <summary>
/// The solarise
/// </summary>
Solarise,
/// <summary>
/// The whiteboard
/// </summary>
Whiteboard,
/// <summary>
/// The blackboard
/// </summary>
Blackboard,
/// <summary>
/// The sketch
/// </summary>
Sketch,
/// <summary>
/// The denoise
/// </summary>
Denoise,
/// <summary>
/// The emboss
/// </summary>
Emboss,
/// <summary>
/// The oil paint
/// </summary>
OilPaint,
/// <summary>
/// The hatch
/// </summary>
Hatch,
/// <summary>
/// Graphite Pen
/// </summary>
GPen,
/// <summary>
/// The pastel
/// </summary>
Pastel,
/// <summary>
/// The water colour
/// </summary>
WaterColour,
/// <summary>
/// The film
/// </summary>
Film,
/// <summary>
/// The blur
/// </summary>
Blur,
/// <summary>
/// The saturation
/// </summary>
Saturation,
/// <summary>
/// The solour swap
/// </summary>
SolourSwap,
/// <summary>
/// The washed out
/// </summary>
WashedOut,
/// <summary>
/// The colour point
/// </summary>
ColourPoint,
/// <summary>
/// The colour balance
/// </summary>
ColourBalance,
/// <summary>
/// The cartoon
/// </summary>
Cartoon
}
/// <summary>
/// Defines the different metering modes for the Raspberry Pi's camera module
/// </summary>
public enum CameraMeteringMode {
/// <summary>
/// The average
/// </summary>
Average,
/// <summary>
/// The spot
/// </summary>
Spot,
/// <summary>
/// The backlit
/// </summary>
Backlit,
/// <summary>
/// The matrix
/// </summary>
Matrix,
}
/// <summary>
/// Defines the different image rotation modes for the Raspberry Pi's camera module
/// </summary>
public enum CameraImageRotation {
/// <summary>
/// No rerotation
/// </summary>
None = 0,
/// <summary>
/// 90 Degrees
/// </summary>
Degrees90 = 90,
/// <summary>
/// 180 Degrees
/// </summary>
Degrees180 = 180,
/// <summary>
/// 270 degrees
/// </summary>
Degrees270 = 270
}
/// <summary>
/// Defines the different DRC (Dynamic Range Compensation) modes for the Raspberry Pi's camera module
/// Helpful for low light photos
/// </summary>
public enum CameraDynamicRangeCompensation {
/// <summary>
/// The off setting
/// </summary>
Off,
/// <summary>
/// The low
/// </summary>
Low,
/// <summary>
/// The medium
/// </summary>
Medium,
/// <summary>
/// The high
/// </summary>
High
}
/// <summary>
/// Defines the bit-wise mask flags for the available annotation elements for the Raspberry Pi's camera module
/// </summary>
[Flags]
public enum CameraAnnotation {
/// <summary>
/// The none
/// </summary>
None = 0,
/// <summary>
/// The time
/// </summary>
Time = 4,
/// <summary>
/// The date
/// </summary>
Date = 8,
/// <summary>
/// The shutter settings
/// </summary>
ShutterSettings = 16,
/// <summary>
/// The caf settings
/// </summary>
CafSettings = 32,
/// <summary>
/// The gain settings
/// </summary>
GainSettings = 64,
/// <summary>
/// The lens settings
/// </summary>
LensSettings = 128,
/// <summary>
/// The motion settings
/// </summary>
MotionSettings = 256,
/// <summary>
/// The frame number
/// </summary>
FrameNumber = 512,
/// <summary>
/// The solid background
/// </summary>
SolidBackground = 1024,
}
/// <summary>
/// Defines the different H.264 encoding profiles to be used when capturing video.
/// </summary>
public enum CameraH264Profile {
/// <summary>
/// BP: Primarily for lower-cost applications with limited computing resources,
/// this profile is used widely in videoconferencing and mobile applications.
/// </summary>
Baseline,
/// <summary>
/// MP: Originally intended as the mainstream consumer profile for broadcast
/// and storage applications, the importance of this profile faded when the High profile was developed for those applications.
/// </summary>
Main,
/// <summary>
/// HiP: The primary profile for broadcast and disc storage applications, particularly
/// for high-definition television applications (this is the profile adopted into HD DVD and Blu-ray Disc, for example).
/// </summary>
High
}
} }

View File

@ -1,80 +1,66 @@
namespace Unosquare.RaspberryIO.Computer using Unosquare.Swan.Abstractions;
{ using System.Globalization;
using Swan.Abstractions; using System.IO;
using System.Globalization; using System;
using System.IO;
namespace Unosquare.RaspberryIO.Computer {
/// <summary>
/// The Official Raspberry Pi 7-inch touch display from the foundation
/// Some docs available here:
/// http://forums.pimoroni.com/t/official-7-raspberry-pi-touch-screen-faq/959
/// </summary>
public class DsiDisplay : SingletonBase<DsiDisplay> {
private const String BacklightFilename = "/sys/class/backlight/rpi_backlight/bl_power";
private const String BrightnessFilename = "/sys/class/backlight/rpi_backlight/brightness";
/// <summary> /// <summary>
/// The Official Raspberry Pi 7-inch touch display from the foundation /// Prevents a default instance of the <see cref="DsiDisplay"/> class from being created.
/// Some docs available here:
/// http://forums.pimoroni.com/t/official-7-raspberry-pi-touch-screen-faq/959
/// </summary> /// </summary>
public class DsiDisplay : SingletonBase<DsiDisplay> private DsiDisplay() {
{ // placeholder
private const string BacklightFilename = "/sys/class/backlight/rpi_backlight/bl_power"; }
private const string BrightnessFilename = "/sys/class/backlight/rpi_backlight/brightness";
/// <summary>
/// <summary> /// Gets a value indicating whether the Pi Foundation Display files are present.
/// Prevents a default instance of the <see cref="DsiDisplay"/> class from being created. /// </summary>
/// </summary> /// <value>
private DsiDisplay() /// <c>true</c> if this instance is present; otherwise, <c>false</c>.
{ /// </value>
// placeholder public Boolean IsPresent => File.Exists(BrightnessFilename);
}
/// <summary>
/// <summary> /// Gets or sets the brightness of the DSI display via filesystem.
/// Gets a value indicating whether the Pi Foundation Display files are present. /// </summary>
/// </summary> /// <value>
/// <value> /// The brightness.
/// <c>true</c> if this instance is present; otherwise, <c>false</c>. /// </value>
/// </value> public Byte Brightness {
public bool IsPresent => File.Exists(BrightnessFilename); get => this.IsPresent == false ? (Byte)0 : Byte.TryParse(File.ReadAllText(BrightnessFilename).Trim(), out Byte brightness) ? brightness : (Byte)0;
set {
/// <summary> if(this.IsPresent == false) {
/// Gets or sets the brightness of the DSI display via filesystem. return;
/// </summary> }
/// <value>
/// The brightness. File.WriteAllText(BrightnessFilename, value.ToString(CultureInfo.InvariantCulture));
/// </value> }
public byte Brightness }
{
get /// <summary>
{ /// Gets or sets a value indicating whether the backlight of the DSI display on.
if (IsPresent == false) return 0; /// This operation is performed via the file system
/// </summary>
return byte.TryParse(File.ReadAllText(BrightnessFilename).Trim(), out var brightness) ? brightness : (byte)0; /// <value>
} /// <c>true</c> if this instance is backlight on; otherwise, <c>false</c>.
set /// </value>
{ public Boolean IsBacklightOn {
if (IsPresent == false) return; get => this.IsPresent == false ? false : Int32.TryParse(File.ReadAllText(BacklightFilename).Trim(), out Int32 backlight) ? backlight == 0 : false;
File.WriteAllText(BrightnessFilename, value.ToString(CultureInfo.InvariantCulture)); set {
} if(this.IsPresent == false) {
} return;
}
/// <summary>
/// Gets or sets a value indicating whether the backlight of the DSI display on. File.WriteAllText(BacklightFilename, value ? "0" : "1");
/// This operation is performed via the file system }
/// </summary> }
/// <value> }
/// <c>true</c> if this instance is backlight on; otherwise, <c>false</c>.
/// </value>
public bool IsBacklightOn
{
get
{
if (IsPresent == false) return false;
if (int.TryParse(File.ReadAllText(BacklightFilename).Trim(), out var backlight))
return backlight == 0;
return false;
}
set
{
if (IsPresent == false) return;
File.WriteAllText(BacklightFilename, value ? "0" : "1");
}
}
}
} }

View File

@ -1,40 +1,51 @@
namespace Unosquare.RaspberryIO.Computer using System;
{ using System.Net;
using System.Net;
namespace Unosquare.RaspberryIO.Computer {
/// <summary>
/// Represents a Network Adapter
/// </summary>
public class NetworkAdapterInfo {
/// <summary> /// <summary>
/// Represents a Network Adapter /// Gets the name.
/// </summary> /// </summary>
public class NetworkAdapterInfo public String Name {
{ get; internal set;
/// <summary> }
/// Gets the name.
/// </summary> /// <summary>
public string Name { get; internal set; } /// Gets the IP V4 address.
/// </summary>
/// <summary> public IPAddress IPv4 {
/// Gets the IP V4 address. get; internal set;
/// </summary> }
public IPAddress IPv4 { get; internal set; }
/// <summary>
/// <summary> /// Gets the IP V6 address.
/// Gets the IP V6 address. /// </summary>
/// </summary> public IPAddress IPv6 {
public IPAddress IPv6 { get; internal set; } get; internal set;
}
/// <summary>
/// Gets the name of the access point. /// <summary>
/// </summary> /// Gets the name of the access point.
public string AccessPointName { get; internal set; } /// </summary>
public String AccessPointName {
/// <summary> get; internal set;
/// Gets the MAC (Physical) address. }
/// </summary>
public string MacAddress { get; internal set; } /// <summary>
/// Gets the MAC (Physical) address.
/// <summary> /// </summary>
/// Gets a value indicating whether this instance is wireless. public String MacAddress {
/// </summary> get; internal set;
public bool IsWireless { get; internal set; } }
}
/// <summary>
/// Gets a value indicating whether this instance is wireless.
/// </summary>
public Boolean IsWireless {
get; internal set;
}
}
} }

View File

@ -1,266 +1,252 @@
namespace Unosquare.RaspberryIO.Computer using Unosquare.Swan;
{ using Unosquare.Swan.Abstractions;
using Swan; using Unosquare.Swan.Components;
using Swan.Abstractions; using System;
using Swan.Components; using System.Collections.Generic;
using System; using System.IO;
using System.Collections.Generic; using System.Linq;
using System.IO; using System.Net;
using System.Linq; using System.Text;
using System.Net;
using System.Text; namespace Unosquare.RaspberryIO.Computer {
/// <summary>
/// Represents the network information
/// </summary>
public class NetworkSettings : SingletonBase<NetworkSettings> {
private const String EssidTag = "ESSID:";
/// <summary> /// <summary>
/// Represents the network information /// Gets the local machine Host Name.
/// </summary> /// </summary>
public class NetworkSettings : SingletonBase<NetworkSettings> public String HostName => Network.HostName;
{
private const string EssidTag = "ESSID:"; /// <summary>
/// Retrieves the wireless networks.
/// <summary> /// </summary>
/// Gets the local machine Host Name. /// <param name="adapter">The adapter.</param>
/// </summary> /// <returns>A list of WiFi networks</returns>
public string HostName => Network.HostName; public List<WirelessNetworkInfo> RetrieveWirelessNetworks(String adapter) => this.RetrieveWirelessNetworks(new[] { adapter });
/// <summary> /// <summary>
/// Retrieves the wireless networks. /// Retrieves the wireless networks.
/// </summary> /// </summary>
/// <param name="adapter">The adapter.</param> /// <param name="adapters">The adapters.</param>
/// <returns>A list of WiFi networks</returns> /// <returns>A list of WiFi networks</returns>
public List<WirelessNetworkInfo> RetrieveWirelessNetworks(string adapter) => RetrieveWirelessNetworks(new[] { adapter }); public List<WirelessNetworkInfo> RetrieveWirelessNetworks(String[] adapters = null) {
List<WirelessNetworkInfo> result = new List<WirelessNetworkInfo>();
/// <summary>
/// Retrieves the wireless networks. foreach(String networkAdapter in adapters ?? this.RetrieveAdapters().Where(x => x.IsWireless).Select(x => x.Name)) {
/// </summary> String wirelessOutput = ProcessRunner.GetProcessOutputAsync("iwlist", $"{networkAdapter} scanning").Result;
/// <param name="adapters">The adapters.</param> String[] outputLines =
/// <returns>A list of WiFi networks</returns> wirelessOutput.Split('\n')
public List<WirelessNetworkInfo> RetrieveWirelessNetworks(string[] adapters = null) .Select(x => x.Trim())
{ .Where(x => String.IsNullOrWhiteSpace(x) == false)
var result = new List<WirelessNetworkInfo>(); .ToArray();
foreach (var networkAdapter in adapters ?? RetrieveAdapters().Where(x => x.IsWireless).Select(x => x.Name)) for(Int32 i = 0; i < outputLines.Length; i++) {
{ String line = outputLines[i];
var wirelessOutput = ProcessRunner.GetProcessOutputAsync("iwlist", $"{networkAdapter} scanning").Result;
var outputLines = if(line.StartsWith(EssidTag) == false) {
wirelessOutput.Split('\n') continue;
.Select(x => x.Trim()) }
.Where(x => string.IsNullOrWhiteSpace(x) == false)
.ToArray(); WirelessNetworkInfo network = new WirelessNetworkInfo() {
Name = line.Replace(EssidTag, String.Empty).Replace("\"", String.Empty)
for (var i = 0; i < outputLines.Length; i++) };
{
var line = outputLines[i]; while(true) {
if(i + 1 >= outputLines.Length) {
if (line.StartsWith(EssidTag) == false) continue; break;
}
var network = new WirelessNetworkInfo()
{ // should look for two lines before the ESSID acording to the scan
Name = line.Replace(EssidTag, string.Empty).Replace("\"", string.Empty) line = outputLines[i - 2];
};
if(line.StartsWith("Quality=")) {
while (true) network.Quality = line.Replace("Quality=", String.Empty);
{ break;
if (i + 1 >= outputLines.Length) break; }
}
// should look for two lines before the ESSID acording to the scan
line = outputLines[i - 2]; while(true) {
if(i + 1 >= outputLines.Length) {
if (line.StartsWith("Quality=")) break;
{ }
network.Quality = line.Replace("Quality=", string.Empty);
break; // should look for a line before the ESSID acording to the scan
} line = outputLines[i - 1];
}
if(line.StartsWith("Encryption key:")) {
while (true) network.IsEncrypted = line.Replace("Encryption key:", String.Empty).Trim() == "on";
{ break;
if (i + 1 >= outputLines.Length) break; }
}
// should look for a line before the ESSID acording to the scan
line = outputLines[i - 1]; if(result.Any(x => x.Name == network.Name) == false) {
result.Add(network);
if (line.StartsWith("Encryption key:")) }
{ }
network.IsEncrypted = line.Replace("Encryption key:", string.Empty).Trim() == "on"; }
break;
} return result.OrderBy(x => x.Name).ToList();
} }
if (result.Any(x => x.Name == network.Name) == false) /// <summary>
result.Add(network); /// Setups the wireless network.
} /// </summary>
} /// <param name="adapterName">Name of the adapter.</param>
/// <param name="networkSsid">The network ssid.</param>
return result.OrderBy(x => x.Name).ToList(); /// <param name="password">The password.</param>
} /// <param name="countryCode">The 2-letter country code in uppercase. Default is US.</param>
/// <returns>True if successful. Otherwise, false.</returns>
/// <summary> public Boolean SetupWirelessNetwork(String adapterName, String networkSsid, String password = null, String countryCode = "US") {
/// Setups the wireless network. // TODO: Get the country where the device is located to set 'country' param in payload var
/// </summary> String payload = $"country={countryCode}\nctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\nupdate_config=1\n";
/// <param name="adapterName">Name of the adapter.</param> payload += String.IsNullOrEmpty(password)
/// <param name="networkSsid">The network ssid.</param> ? $"network={{\n\tssid=\"{networkSsid}\"\n\t}}\n"
/// <param name="password">The password.</param> : $"network={{\n\tssid=\"{networkSsid}\"\n\tpsk=\"{password}\"\n\t}}\n";
/// <param name="countryCode">The 2-letter country code in uppercase. Default is US.</param> try {
/// <returns>True if successful. Otherwise, false.</returns> File.WriteAllText("/etc/wpa_supplicant/wpa_supplicant.conf", payload);
public bool SetupWirelessNetwork(string adapterName, string networkSsid, string password = null, string countryCode = "US") ProcessRunner.GetProcessOutputAsync("pkill", "-f wpa_supplicant").Wait();
{ ProcessRunner.GetProcessOutputAsync("ifdown", adapterName).Wait();
// TODO: Get the country where the device is located to set 'country' param in payload var ProcessRunner.GetProcessOutputAsync("ifup", adapterName).Wait();
var payload = $"country={countryCode}\nctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\nupdate_config=1\n"; } catch(Exception ex) {
payload += string.IsNullOrEmpty(password) ex.Log(nameof(NetworkSettings));
? $"network={{\n\tssid=\"{networkSsid}\"\n\t}}\n" return false;
: $"network={{\n\tssid=\"{networkSsid}\"\n\tpsk=\"{password}\"\n\t}}\n"; }
try
{ return true;
File.WriteAllText("/etc/wpa_supplicant/wpa_supplicant.conf", payload); }
ProcessRunner.GetProcessOutputAsync("pkill", "-f wpa_supplicant").Wait();
ProcessRunner.GetProcessOutputAsync("ifdown", adapterName).Wait(); /// <summary>
ProcessRunner.GetProcessOutputAsync("ifup", adapterName).Wait(); /// Retrieves the network adapters.
} /// </summary>
catch (Exception ex) /// <returns>A list of network adapters.</returns>
{ public List<NetworkAdapterInfo> RetrieveAdapters() {
ex.Log(nameof(NetworkSettings)); const String hWaddr = "HWaddr ";
return false; const String ether = "ether ";
}
List<NetworkAdapterInfo> result = new List<NetworkAdapterInfo>();
return true; String interfacesOutput = ProcessRunner.GetProcessOutputAsync("ifconfig").Result;
} String[] wlanOutput = ProcessRunner.GetProcessOutputAsync("iwconfig")
.Result.Split('\n')
/// <summary> .Where(x => x.Contains("no wireless extensions.") == false)
/// Retrieves the network adapters. .ToArray();
/// </summary>
/// <returns>A list of network adapters.</returns> String[] outputLines = interfacesOutput.Split('\n').Where(x => String.IsNullOrWhiteSpace(x) == false).ToArray();
public List<NetworkAdapterInfo> RetrieveAdapters()
{ for(Int32 i = 0; i < outputLines.Length; i++) {
const string hWaddr = "HWaddr "; // grab the current line
const string ether = "ether "; String line = outputLines[i];
var result = new List<NetworkAdapterInfo>(); // skip if the line is indented
var interfacesOutput = ProcessRunner.GetProcessOutputAsync("ifconfig").Result; if(Char.IsLetterOrDigit(line[0]) == false) {
var wlanOutput = ProcessRunner.GetProcessOutputAsync("iwconfig") continue;
.Result.Split('\n') }
.Where(x => x.Contains("no wireless extensions.") == false)
.ToArray(); // Read the line as an adatper
NetworkAdapterInfo adapter = new NetworkAdapterInfo {
var outputLines = interfacesOutput.Split('\n').Where(x => string.IsNullOrWhiteSpace(x) == false).ToArray(); Name = line.Substring(0, line.IndexOf(' ')).TrimEnd(':')
};
for (var i = 0; i < outputLines.Length; i++)
{ // Parse the MAC address in old version of ifconfig; it comes in the first line
// grab the current line if(line.IndexOf(hWaddr) >= 0) {
var line = outputLines[i]; Int32 startIndexHwd = line.IndexOf(hWaddr) + hWaddr.Length;
adapter.MacAddress = line.Substring(startIndexHwd, 17).Trim();
// skip if the line is indented }
if (char.IsLetterOrDigit(line[0]) == false)
continue; // Parse the info in lines other than the first
for(Int32 j = i + 1; j < outputLines.Length; j++) {
// Read the line as an adatper // Get the contents of the indented line
var adapter = new NetworkAdapterInfo String indentedLine = outputLines[j];
{
Name = line.Substring(0, line.IndexOf(' ')).TrimEnd(':') // We have hit the next adapter info
}; if(Char.IsLetterOrDigit(indentedLine[0])) {
i = j - 1;
// Parse the MAC address in old version of ifconfig; it comes in the first line break;
if (line.IndexOf(hWaddr) >= 0) }
{
var startIndexHwd = line.IndexOf(hWaddr) + hWaddr.Length; // Parse the MAC address in new versions of ifconfig; it no longer comes in the first line
adapter.MacAddress = line.Substring(startIndexHwd, 17).Trim(); if(indentedLine.IndexOf(ether) >= 0 && String.IsNullOrWhiteSpace(adapter.MacAddress)) {
} Int32 startIndexHwd = indentedLine.IndexOf(ether) + ether.Length;
adapter.MacAddress = indentedLine.Substring(startIndexHwd, 17).Trim();
// Parse the info in lines other than the first }
for (var j = i + 1; j < outputLines.Length; j++)
{ // Parse the IPv4 Address
// Get the contents of the indented line {
var indentedLine = outputLines[j]; String addressText = ParseOutputTagFromLine(indentedLine, "inet addr:") ?? ParseOutputTagFromLine(indentedLine, "inet ");
// We have hit the next adapter info if(addressText != null) {
if (char.IsLetterOrDigit(indentedLine[0])) if(IPAddress.TryParse(addressText, out IPAddress outValue)) {
{ adapter.IPv4 = outValue;
i = j - 1; }
break; }
} }
// Parse the MAC address in new versions of ifconfig; it no longer comes in the first line // Parse the IPv6 Address
if (indentedLine.IndexOf(ether) >= 0 && string.IsNullOrWhiteSpace(adapter.MacAddress)) {
{ String addressText = ParseOutputTagFromLine(indentedLine, "inet6 addr:") ?? ParseOutputTagFromLine(indentedLine, "inet6 ");
var startIndexHwd = indentedLine.IndexOf(ether) + ether.Length;
adapter.MacAddress = indentedLine.Substring(startIndexHwd, 17).Trim(); if(addressText != null) {
} if(IPAddress.TryParse(addressText, out IPAddress outValue)) {
adapter.IPv6 = outValue;
// Parse the IPv4 Address }
{ }
var addressText = ParseOutputTagFromLine(indentedLine, "inet addr:") ?? ParseOutputTagFromLine(indentedLine, "inet "); }
if (addressText != null) // we have hit the end of the output in an indented line
{ if(j >= outputLines.Length - 1) {
if (IPAddress.TryParse(addressText, out var outValue)) i = outputLines.Length;
adapter.IPv4 = outValue; }
} }
}
// Retrieve the wireless LAN info
// Parse the IPv6 Address String wlanInfo = wlanOutput.FirstOrDefault(x => x.StartsWith(adapter.Name));
{
var addressText = ParseOutputTagFromLine(indentedLine, "inet6 addr:") ?? ParseOutputTagFromLine(indentedLine, "inet6 "); if(wlanInfo != null) {
adapter.IsWireless = true;
if (addressText != null) String[] essidParts = wlanInfo.Split(new[] { EssidTag }, StringSplitOptions.RemoveEmptyEntries);
{ if(essidParts.Length >= 2) {
if (IPAddress.TryParse(addressText, out var outValue)) adapter.AccessPointName = essidParts[1].Replace("\"", String.Empty).Trim();
adapter.IPv6 = outValue; }
} }
}
// Add the current adapter to the result
// we have hit the end of the output in an indented line result.Add(adapter);
if (j >= outputLines.Length - 1) }
i = outputLines.Length;
} return result.OrderBy(x => x.Name).ToList();
}
// Retrieve the wireless LAN info
var wlanInfo = wlanOutput.FirstOrDefault(x => x.StartsWith(adapter.Name)); /// <summary>
/// Retrieves current wireless connected network name.
if (wlanInfo != null) /// </summary>
{ /// <returns>The connected network name.</returns>
adapter.IsWireless = true; public String GetWirelessNetworkName() => ProcessRunner.GetProcessOutputAsync("iwgetid", "-r").Result;
var essidParts = wlanInfo.Split(new[] { EssidTag }, StringSplitOptions.RemoveEmptyEntries);
if (essidParts.Length >= 2) /// <summary>
{ /// Parses the output tag from the given line.
adapter.AccessPointName = essidParts[1].Replace("\"", string.Empty).Trim(); /// </summary>
} /// <param name="indentedLine">The indented line.</param>
} /// <param name="tagName">Name of the tag.</param>
/// <returns>The value after the tag identifier</returns>
// Add the current adapter to the result private static String ParseOutputTagFromLine(String indentedLine, String tagName) {
result.Add(adapter); if(indentedLine.IndexOf(tagName) < 0) {
} return null;
}
return result.OrderBy(x => x.Name).ToList();
} Int32 startIndex = indentedLine.IndexOf(tagName) + tagName.Length;
StringBuilder builder = new StringBuilder(1024);
/// <summary> for(Int32 c = startIndex; c < indentedLine.Length; c++) {
/// Retrieves current wireless connected network name. Char currentChar = indentedLine[c];
/// </summary> if(!Char.IsPunctuation(currentChar) && !Char.IsLetterOrDigit(currentChar)) {
/// <returns>The connected network name.</returns> break;
public string GetWirelessNetworkName() => ProcessRunner.GetProcessOutputAsync("iwgetid", "-r").Result; }
/// <summary> _ = builder.Append(currentChar);
/// Parses the output tag from the given line. }
/// </summary>
/// <param name="indentedLine">The indented line.</param> return builder.ToString();
/// <param name="tagName">Name of the tag.</param> }
/// <returns>The value after the tag identifier</returns> }
private static string ParseOutputTagFromLine(string indentedLine, string tagName)
{
if (indentedLine.IndexOf(tagName) < 0)
return null;
var startIndex = indentedLine.IndexOf(tagName) + tagName.Length;
var builder = new StringBuilder(1024);
for (var c = startIndex; c < indentedLine.Length; c++)
{
var currentChar = indentedLine[c];
if (!char.IsPunctuation(currentChar) && !char.IsLetterOrDigit(currentChar))
break;
builder.Append(currentChar);
}
return builder.ToString();
}
}
} }

View File

@ -1,46 +1,58 @@
namespace Unosquare.RaspberryIO.Computer using System;
{
namespace Unosquare.RaspberryIO.Computer {
/// <summary>
/// Represents the OS Information
/// </summary>
public class OsInfo {
/// <summary> /// <summary>
/// Represents the OS Information /// System name
/// </summary> /// </summary>
public class OsInfo public String SysName {
{ get; set;
/// <summary> }
/// System name
/// </summary> /// <summary>
public string SysName { get; set; } /// Node name
/// </summary>
/// <summary> public String NodeName {
/// Node name get; set;
/// </summary> }
public string NodeName { get; set; }
/// <summary>
/// <summary> /// Release level
/// Release level /// </summary>
/// </summary> public String Release {
public string Release { get; set; } get; set;
}
/// <summary>
/// Version level /// <summary>
/// </summary> /// Version level
public string Version { get; set; } /// </summary>
public String Version {
/// <summary> get; set;
/// Hardware level }
/// </summary>
public string Machine { get; set; } /// <summary>
/// Hardware level
/// <summary> /// </summary>
/// Domain name public String Machine {
/// </summary> get; set;
public string DomainName { get; set; } }
/// <summary> /// <summary>
/// Returns a <see cref="string" /> that represents this instance. /// Domain name
/// </summary> /// </summary>
/// <returns> public String DomainName {
/// A <see cref="string" /> that represents this instance. get; set;
/// </returns> }
public override string ToString() => $"{SysName} {Release} {Version}";
} /// <summary>
/// Returns a <see cref="String" /> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="String" /> that represents this instance.
/// </returns>
public override String ToString() => $"{this.SysName} {this.Release} {this.Version}";
}
} }

View File

@ -1,134 +1,132 @@
namespace Unosquare.RaspberryIO.Computer namespace Unosquare.RaspberryIO.Computer {
{ /// <summary>
/// Defines the board revision codes of the different versions of the Raspberry Pi
/// http://www.raspberrypi-spy.co.uk/2012/09/checking-your-raspberry-pi-board-version/
/// </summary>
public enum PiVersion {
/// <summary> /// <summary>
/// Defines the board revision codes of the different versions of the Raspberry Pi /// The unknown version
/// http://www.raspberrypi-spy.co.uk/2012/09/checking-your-raspberry-pi-board-version/
/// </summary> /// </summary>
public enum PiVersion Unknown = 0,
{
/// <summary> /// <summary>
/// The unknown version /// The model b rev1
/// </summary> /// </summary>
Unknown = 0, ModelBRev1 = 0x0002,
/// <summary> /// <summary>
/// The model b rev1 /// The model b rev1 ec N0001
/// </summary> /// </summary>
ModelBRev1 = 0x0002, ModelBRev1ECN0001 = 0x0003,
/// <summary> /// <summary>
/// The model b rev1 ec N0001 /// The model b rev2x04
/// </summary> /// </summary>
ModelBRev1ECN0001 = 0x0003, ModelBRev2x04 = 0x0004,
/// <summary> /// <summary>
/// The model b rev2x04 /// The model b rev2x05
/// </summary> /// </summary>
ModelBRev2x04 = 0x0004, ModelBRev2x05 = 0x0005,
/// <summary> /// <summary>
/// The model b rev2x05 /// The model b rev2x06
/// </summary> /// </summary>
ModelBRev2x05 = 0x0005, ModelBRev2x06 = 0x0006,
/// <summary> /// <summary>
/// The model b rev2x06 /// The model ax07
/// </summary> /// </summary>
ModelBRev2x06 = 0x0006, ModelAx07 = 0x0007,
/// <summary> /// <summary>
/// The model ax07 /// The model ax08
/// </summary> /// </summary>
ModelAx07 = 0x0007, ModelAx08 = 0x0008,
/// <summary> /// <summary>
/// The model ax08 /// The model ax09
/// </summary> /// </summary>
ModelAx08 = 0x0008, ModelAx09 = 0x0009,
/// <summary> /// <summary>
/// The model ax09 /// The model b rev2x0d
/// </summary> /// </summary>
ModelAx09 = 0x0009, ModelBRev2x0d,
/// <summary> /// <summary>
/// The model b rev2x0d /// The model b rev2x0e
/// </summary> /// </summary>
ModelBRev2x0d, ModelBRev2x0e,
/// <summary> /// <summary>
/// The model b rev2x0e /// The model b rev2x0f
/// </summary> /// </summary>
ModelBRev2x0e, ModelBRev2x0f = 0x000f,
/// <summary> /// <summary>
/// The model b rev2x0f /// The model b plus0x10
/// </summary> /// </summary>
ModelBRev2x0f = 0x000f, ModelBPlus0x10 = 0x0010,
/// <summary> /// <summary>
/// The model b plus0x10 /// The model b plus0x13
/// </summary> /// </summary>
ModelBPlus0x10 = 0x0010, ModelBPlus0x13 = 0x0013,
/// <summary> /// <summary>
/// The model b plus0x13 /// The compute module0x11
/// </summary> /// </summary>
ModelBPlus0x13 = 0x0013, ComputeModule0x11 = 0x0011,
/// <summary> /// <summary>
/// The compute module0x11 /// The compute module0x14
/// </summary> /// </summary>
ComputeModule0x11 = 0x0011, ComputeModule0x14 = 0x0014,
/// <summary> /// <summary>
/// The compute module0x14 /// The model a plus0x12
/// </summary> /// </summary>
ComputeModule0x14 = 0x0014, ModelAPlus0x12 = 0x0012,
/// <summary> /// <summary>
/// The model a plus0x12 /// The model a plus0x15
/// </summary> /// </summary>
ModelAPlus0x12 = 0x0012, ModelAPlus0x15 = 0x0015,
/// <summary> /// <summary>
/// The model a plus0x15 /// The pi2 model B1V1 sony
/// </summary> /// </summary>
ModelAPlus0x15 = 0x0015, Pi2ModelB1v1Sony = 0xa01041,
/// <summary> /// <summary>
/// The pi2 model B1V1 sony /// The pi2 model B1V1 embest
/// </summary> /// </summary>
Pi2ModelB1v1Sony = 0xa01041, Pi2ModelB1v1Embest = 0xa21041,
/// <summary> /// <summary>
/// The pi2 model B1V1 embest /// The pi2 model B1V2
/// </summary> /// </summary>
Pi2ModelB1v1Embest = 0xa21041, Pi2ModelB1v2 = 0xa22042,
/// <summary> /// <summary>
/// The pi2 model B1V2 /// The pi zero1v2
/// </summary> /// </summary>
Pi2ModelB1v2 = 0xa22042, PiZero1v2 = 0x900092,
/// <summary> /// <summary>
/// The pi zero1v2 /// The pi zero1v3
/// </summary> /// </summary>
PiZero1v2 = 0x900092, PiZero1v3 = 0x900093,
/// <summary> /// <summary>
/// The pi zero1v3 /// The pi3 model b sony
/// </summary> /// </summary>
PiZero1v3 = 0x900093, Pi3ModelBSony = 0xa02082,
/// <summary> /// <summary>
/// The pi3 model b sony /// The pi3 model b embest
/// </summary> /// </summary>
Pi3ModelBSony = 0xa02082, Pi3ModelBEmbest = 0xa22082
}
/// <summary>
/// The pi3 model b embest
/// </summary>
Pi3ModelBEmbest = 0xa22082
}
} }

View File

@ -1,344 +1,343 @@
namespace Unosquare.RaspberryIO.Computer using Unosquare.RaspberryIO.Native;
{ using Unosquare.Swan.Abstractions;
using Native; using System;
using Swan.Abstractions; using System.Collections.Generic;
using System; using System.Globalization;
using System.Collections.Generic; using System.IO;
using System.Globalization; using System.Linq;
using System.IO; using System.Reflection;
using System.Linq;
using System.Reflection; namespace Unosquare.RaspberryIO.Computer {
/// <summary>
/// http://raspberry-pi-guide.readthedocs.io/en/latest/system.html
/// </summary>
public sealed class SystemInfo : SingletonBase<SystemInfo> {
private const String CpuInfoFilePath = "/proc/cpuinfo";
private const String MemInfoFilePath = "/proc/meminfo";
private const String UptimeFilePath = "/proc/uptime";
private static readonly StringComparer StringComparer = StringComparer.InvariantCultureIgnoreCase;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0052:Ungelesene private Member entfernen", Justification = "<Ausstehend>")]
private static readonly Object SyncRoot = new Object();
/// <summary> /// <summary>
/// http://raspberry-pi-guide.readthedocs.io/en/latest/system.html /// Prevents a default instance of the <see cref="SystemInfo"/> class from being created.
/// </summary> /// </summary>
public sealed class SystemInfo : SingletonBase<SystemInfo> /// <exception cref="NotSupportedException">Could not initialize the GPIO controller</exception>
{ private SystemInfo() {
private const string CpuInfoFilePath = "/proc/cpuinfo"; #region Obtain and format a property dictionary
private const string MemInfoFilePath = "/proc/meminfo";
private const string UptimeFilePath = "/proc/uptime"; PropertyInfo[] properties =
private static readonly StringComparer StringComparer = StringComparer.InvariantCultureIgnoreCase; typeof(SystemInfo).GetTypeInfo()
.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
private static readonly object SyncRoot = new object(); .Where(
p =>
/// <summary> p.CanWrite && p.CanRead &&
/// Prevents a default instance of the <see cref="SystemInfo"/> class from being created. (p.PropertyType == typeof(String) || p.PropertyType == typeof(String[])))
/// </summary> .ToArray();
/// <exception cref="NotSupportedException">Could not initialize the GPIO controller</exception> Dictionary<String, PropertyInfo> propDictionary = new Dictionary<String, PropertyInfo>(StringComparer);
private SystemInfo()
{ foreach(PropertyInfo prop in properties) {
#region Obtain and format a property dictionary propDictionary[prop.Name.Replace(" ", String.Empty).ToLowerInvariant().Trim()] = prop;
}
var properties =
typeof(SystemInfo).GetTypeInfo() #endregion
.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where( #region Extract CPU information
p =>
p.CanWrite && p.CanRead && if(File.Exists(CpuInfoFilePath)) {
(p.PropertyType == typeof(string) || p.PropertyType == typeof(string[]))) String[] cpuInfoLines = File.ReadAllLines(CpuInfoFilePath);
.ToArray();
var propDictionary = new Dictionary<string, PropertyInfo>(StringComparer); foreach(String line in cpuInfoLines) {
String[] lineParts = line.Split(new[] { ':' }, 2);
foreach (var prop in properties) if(lineParts.Length != 2) {
{ continue;
propDictionary[prop.Name.Replace(" ", string.Empty).ToLowerInvariant().Trim()] = prop; }
}
String propertyKey = lineParts[0].Trim().Replace(" ", String.Empty);
#endregion String propertyStringValue = lineParts[1].Trim();
#region Extract CPU information if(!propDictionary.ContainsKey(propertyKey)) {
continue;
if (File.Exists(CpuInfoFilePath)) }
{
var cpuInfoLines = File.ReadAllLines(CpuInfoFilePath); PropertyInfo property = propDictionary[propertyKey];
if(property.PropertyType == typeof(String)) {
foreach (var line in cpuInfoLines) property.SetValue(this, propertyStringValue);
{ } else if(property.PropertyType == typeof(String[])) {
var lineParts = line.Split(new[] { ':' }, 2); String[] propertyArrayAvalue = propertyStringValue.Split(' ');
if (lineParts.Length != 2) property.SetValue(this, propertyArrayAvalue);
continue; }
}
var propertyKey = lineParts[0].Trim().Replace(" ", string.Empty); }
var propertyStringValue = lineParts[1].Trim();
#endregion
if (!propDictionary.ContainsKey(propertyKey)) continue;
#region Extract Memory Information
var property = propDictionary[propertyKey];
if (property.PropertyType == typeof(string)) if(File.Exists(MemInfoFilePath)) {
{ String[] memInfoLines = File.ReadAllLines(MemInfoFilePath);
property.SetValue(this, propertyStringValue); foreach(String line in memInfoLines) {
} String[] lineParts = line.Split(new[] { ':' }, 2);
else if (property.PropertyType == typeof(string[])) if(lineParts.Length != 2) {
{ continue;
var propertyArrayAvalue = propertyStringValue.Split(' '); }
property.SetValue(this, propertyArrayAvalue);
} if(lineParts[0].ToLowerInvariant().Trim().Equals("memtotal") == false) {
} continue;
} }
#endregion String memKb = lineParts[1].ToLowerInvariant().Trim().Replace("kb", String.Empty).Trim();
#region Extract Memory Information if(Int32.TryParse(memKb, out Int32 parsedMem)) {
this.InstalledRam = parsedMem * 1024;
if (File.Exists(MemInfoFilePath)) break;
{ }
var memInfoLines = File.ReadAllLines(MemInfoFilePath); }
foreach (var line in memInfoLines) }
{
var lineParts = line.Split(new[] { ':' }, 2); #endregion
if (lineParts.Length != 2)
continue; #region Board Version and Form Factor
if (lineParts[0].ToLowerInvariant().Trim().Equals("memtotal") == false) try {
continue; if(String.IsNullOrWhiteSpace(this.Revision) == false &&
Int32.TryParse(
var memKb = lineParts[1].ToLowerInvariant().Trim().Replace("kb", string.Empty).Trim(); this.Revision.ToUpperInvariant(),
NumberStyles.HexNumber,
if (int.TryParse(memKb, out var parsedMem)) CultureInfo.InvariantCulture,
{ out Int32 boardVersion)) {
InstalledRam = parsedMem * 1024; this.RaspberryPiVersion = PiVersion.Unknown;
break; if(Enum.GetValues(typeof(PiVersion)).Cast<Int32>().Contains(boardVersion)) {
} this.RaspberryPiVersion = (PiVersion)boardVersion;
} }
} }
#endregion this.WiringPiBoardRevision = WiringPi.PiBoardRev();
} catch {
#region Board Version and Form Factor /* Ignore */
}
try
{ #endregion
if (string.IsNullOrWhiteSpace(Revision) == false &&
int.TryParse( #region Version Information
Revision.ToUpperInvariant(),
NumberStyles.HexNumber, {
CultureInfo.InvariantCulture, String[] libParts = WiringPi.WiringPiLibrary.Split('.');
out var boardVersion)) Int32 major = Int32.Parse(libParts[libParts.Length - 2]);
{ Int32 minor = Int32.Parse(libParts[libParts.Length - 1]);
RaspberryPiVersion = PiVersion.Unknown; Version version = new Version(major, minor);
if (Enum.GetValues(typeof(PiVersion)).Cast<int>().Contains(boardVersion)) this.WiringPiVersion = version;
{ }
RaspberryPiVersion = (PiVersion)boardVersion;
} #endregion
}
#region Extract OS Info
WiringPiBoardRevision = WiringPi.PiBoardRev();
} try {
catch _ = Standard.Uname(out SystemName unameInfo);
{ this.OperatingSystem = new OsInfo {
/* Ignore */ DomainName = unameInfo.DomainName,
} Machine = unameInfo.Machine,
NodeName = unameInfo.NodeName,
#endregion Release = unameInfo.Release,
SysName = unameInfo.SysName,
#region Version Information Version = unameInfo.Version
};
{ } catch {
var libParts = WiringPi.WiringPiLibrary.Split('.'); this.OperatingSystem = new OsInfo();
var major = int.Parse(libParts[libParts.Length - 2]); }
var minor = int.Parse(libParts[libParts.Length - 1]);
var version = new Version(major, minor); #endregion
WiringPiVersion = version; }
}
/// <summary>
#endregion /// Gets the wiring pi library version.
/// </summary>
#region Extract OS Info public Version WiringPiVersion {
get;
try }
{
Standard.Uname(out var unameInfo); /// <summary>
OperatingSystem = new OsInfo /// Gets the OS information.
{ /// </summary>
DomainName = unameInfo.DomainName, /// <value>
Machine = unameInfo.Machine, /// The os information.
NodeName = unameInfo.NodeName, /// </value>
Release = unameInfo.Release, public OsInfo OperatingSystem {
SysName = unameInfo.SysName, get;
Version = unameInfo.Version }
};
} /// <summary>
catch /// Gets the Raspberry Pi version.
{ /// </summary>
OperatingSystem = new OsInfo(); public PiVersion RaspberryPiVersion {
} get;
}
#endregion
} /// <summary>
/// Gets the Wiring Pi board revision (1 or 2).
/// <summary> /// </summary>
/// Gets the wiring pi library version. /// <value>
/// </summary> /// The wiring pi board revision.
public Version WiringPiVersion { get; } /// </value>
public Int32 WiringPiBoardRevision {
/// <summary> get;
/// Gets the OS information. }
/// </summary>
/// <value> /// <summary>
/// The os information. /// Gets the number of processor cores.
/// </value> /// </summary>
public OsInfo OperatingSystem { get; } public Int32 ProcessorCount => Int32.TryParse(this.Processor, out Int32 outIndex) ? outIndex + 1 : 0;
/// <summary> /// <summary>
/// Gets the Raspberry Pi version. /// Gets the installed ram in bytes.
/// </summary> /// </summary>
public PiVersion RaspberryPiVersion { get; } public Int32 InstalledRam {
get;
/// <summary> }
/// Gets the Wiring Pi board revision (1 or 2).
/// </summary> /// <summary>
/// <value> /// Gets a value indicating whether this CPU is little endian.
/// The wiring pi board revision. /// </summary>
/// </value> public Boolean IsLittleEndian => BitConverter.IsLittleEndian;
public int WiringPiBoardRevision { get; }
/// <summary>
/// <summary> /// Gets the CPU model name.
/// Gets the number of processor cores. /// </summary>
/// </summary> public String ModelName {
public int ProcessorCount get; private set;
{ }
get
{ /// <summary>
if (int.TryParse(Processor, out var outIndex)) /// Gets a list of supported CPU features.
{ /// </summary>
return outIndex + 1; public String[] Features {
} get; private set;
}
return 0;
} /// <summary>
} /// Gets the CPU implementer hex code.
/// </summary>
/// <summary> public String CpuImplementer {
/// Gets the installed ram in bytes. get; private set;
/// </summary> }
public int InstalledRam { get; }
/// <summary>
/// <summary> /// Gets the CPU architecture code.
/// Gets a value indicating whether this CPU is little endian. /// </summary>
/// </summary> public String CpuArchitecture {
public bool IsLittleEndian => BitConverter.IsLittleEndian; get; private set;
}
/// <summary>
/// Gets the CPU model name. /// <summary>
/// </summary> /// Gets the CPU variant code.
public string ModelName { get; private set; } /// </summary>
public String CpuVariant {
/// <summary> get; private set;
/// Gets a list of supported CPU features. }
/// </summary>
public string[] Features { get; private set; } /// <summary>
/// Gets the CPU part code.
/// <summary> /// </summary>
/// Gets the CPU implementer hex code. public String CpuPart {
/// </summary> get; private set;
public string CpuImplementer { get; private set; } }
/// <summary> /// <summary>
/// Gets the CPU architecture code. /// Gets the CPU revision code.
/// </summary> /// </summary>
public string CpuArchitecture { get; private set; } public String CpuRevision {
get; private set;
/// <summary> }
/// Gets the CPU variant code.
/// </summary> /// <summary>
public string CpuVariant { get; private set; } /// Gets the hardware model number.
/// </summary>
/// <summary> public String Hardware {
/// Gets the CPU part code. get; private set;
/// </summary> }
public string CpuPart { get; private set; }
/// <summary>
/// <summary> /// Gets the hardware revision number.
/// Gets the CPU revision code. /// </summary>
/// </summary> public String Revision {
public string CpuRevision { get; private set; } get; private set;
}
/// <summary>
/// Gets the hardware model number. /// <summary>
/// </summary> /// Gets the serial number.
public string Hardware { get; private set; } /// </summary>
public String Serial {
/// <summary> get; private set;
/// Gets the hardware revision number. }
/// </summary>
public string Revision { get; private set; } /// <summary>
/// Gets the system uptime (in seconds).
/// <summary> /// </summary>
/// Gets the serial number. public Double Uptime {
/// </summary> get {
public string Serial { get; private set; } try {
if(File.Exists(UptimeFilePath) == false) {
/// <summary> return 0;
/// Gets the system uptime (in seconds). }
/// </summary>
public double Uptime String[] parts = File.ReadAllText(UptimeFilePath).Trim().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
{ if(parts.Length >= 1 && Single.TryParse(parts[0], out Single result)) {
get return result;
{ }
try } catch {
{ /* Ignore */
if (File.Exists(UptimeFilePath) == false) return 0; }
var parts = File.ReadAllText(UptimeFilePath).Trim().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length >= 1 && float.TryParse(parts[0], out var result)) return 0;
return result; }
} }
catch
{ /// <summary>
/* Ignore */ /// Gets the uptime in TimeSpan.
} /// </summary>
public TimeSpan UptimeTimeSpan => TimeSpan.FromSeconds(this.Uptime);
return 0;
} /// <summary>
} /// Placeholder for processor index
/// </summary>
/// <summary> private String Processor {
/// Gets the uptime in TimeSpan. get; set;
/// </summary> }
public TimeSpan UptimeTimeSpan => TimeSpan.FromSeconds(Uptime);
/// <summary>
/// <summary> /// Returns a <see cref="String" /> that represents this instance.
/// Placeholder for processor index /// </summary>
/// </summary> /// <returns>
private string Processor { get; set; } /// A <see cref="String" /> that represents this instance.
/// </returns>
/// <summary> public override String ToString() {
/// Returns a <see cref="string" /> that represents this instance. PropertyInfo[] properties = typeof(SystemInfo).GetTypeInfo().GetProperties(BindingFlags.Instance | BindingFlags.Public)
/// </summary> .Where(p => p.CanRead && (
/// <returns> p.PropertyType == typeof(String) ||
/// A <see cref="string" /> that represents this instance. p.PropertyType == typeof(String[]) ||
/// </returns> p.PropertyType == typeof(Int32) ||
public override string ToString() p.PropertyType == typeof(Boolean) ||
{ p.PropertyType == typeof(TimeSpan)))
var properties = typeof(SystemInfo).GetTypeInfo().GetProperties(BindingFlags.Instance | BindingFlags.Public) .ToArray();
.Where(p => p.CanRead && (
p.PropertyType == typeof(string) || List<String> properyValues = new List<String>
p.PropertyType == typeof(string[]) || {
p.PropertyType == typeof(int) ||
p.PropertyType == typeof(bool) ||
p.PropertyType == typeof(TimeSpan)))
.ToArray();
var properyValues = new List<string>
{
"System Information", "System Information",
$"\t{nameof(WiringPiVersion),-22}: {WiringPiVersion}", $"\t{nameof(this.WiringPiVersion),-22}: {this.WiringPiVersion}",
$"\t{nameof(RaspberryPiVersion),-22}: {RaspberryPiVersion}" $"\t{nameof(this.RaspberryPiVersion),-22}: {this.RaspberryPiVersion}"
}; };
foreach (var property in properties) foreach(PropertyInfo property in properties) {
{ if(property.PropertyType != typeof(String[])) {
if (property.PropertyType != typeof(string[])) properyValues.Add($"\t{property.Name,-22}: {property.GetValue(this)}");
{ } else if(property.GetValue(this) is String[] allValues) {
properyValues.Add($"\t{property.Name,-22}: {property.GetValue(this)}"); String concatValues = String.Join(" ", allValues);
} properyValues.Add($"\t{property.Name,-22}: {concatValues}");
else if (property.GetValue(this) is string[] allValues) }
{ }
var concatValues = string.Join(" ", allValues);
properyValues.Add($"\t{property.Name,-22}: {concatValues}"); return String.Join(Environment.NewLine, properyValues.ToArray());
} }
} }
return string.Join(Environment.NewLine, properyValues.ToArray());
}
}
} }

View File

@ -1,23 +1,29 @@
namespace Unosquare.RaspberryIO.Computer using System;
{
namespace Unosquare.RaspberryIO.Computer {
/// <summary>
/// Represents a wireless network information
/// </summary>
public class WirelessNetworkInfo {
/// <summary> /// <summary>
/// Represents a wireless network information /// Gets the ESSID of the Wireless network.
/// </summary> /// </summary>
public class WirelessNetworkInfo public String Name {
{ get; internal set;
/// <summary> }
/// Gets the ESSID of the Wireless network.
/// </summary> /// <summary>
public string Name { get; internal set; } /// Gets the network quality.
/// </summary>
/// <summary> public String Quality {
/// Gets the network quality. get; internal set;
/// </summary> }
public string Quality { get; internal set; }
/// <summary>
/// <summary> /// Gets a value indicating whether this instance is encrypted.
/// Gets a value indicating whether this instance is encrypted. /// </summary>
/// </summary> public Boolean IsEncrypted {
public bool IsEncrypted { get; internal set; } get; internal set;
} }
}
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,201 +1,167 @@
namespace Unosquare.RaspberryIO.Gpio using System;
{
using System; namespace Unosquare.RaspberryIO.Gpio {
public partial class GpioPin {
public partial class GpioPin #region Static Pin Definitions
{
#region Static Pin Definitions internal static readonly Lazy<GpioPin> Pin08 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin08, 3) {
Capabilities = new[] { PinCapability.GP, PinCapability.I2CSDA },
internal static readonly Lazy<GpioPin> Pin08 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin08, 3) Name = "BCM 2 (SDA)"
{ });
Capabilities = new[] { PinCapability.GP, PinCapability.I2CSDA },
Name = "BCM 2 (SDA)" internal static readonly Lazy<GpioPin> Pin09 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin09, 5) {
}); Capabilities = new[] { PinCapability.GP, PinCapability.I2CSCL },
Name = "BCM 3 (SCL)"
internal static readonly Lazy<GpioPin> Pin09 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin09, 5) });
{
Capabilities = new[] { PinCapability.GP, PinCapability.I2CSCL }, internal static readonly Lazy<GpioPin> Pin07 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin07, 7) {
Name = "BCM 3 (SCL)" Capabilities = new[] { PinCapability.GP, PinCapability.GPCLK },
}); Name = "BCM 4 (GPCLK0)"
});
internal static readonly Lazy<GpioPin> Pin07 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin07, 7)
{ internal static readonly Lazy<GpioPin> Pin00 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin00, 11) {
Capabilities = new[] { PinCapability.GP, PinCapability.GPCLK }, Capabilities = new[] { PinCapability.GP, PinCapability.UARTRTS },
Name = "BCM 4 (GPCLK0)" Name = "BCM 17"
}); });
internal static readonly Lazy<GpioPin> Pin00 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin00, 11) internal static readonly Lazy<GpioPin> Pin02 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin02, 13) {
{ Capabilities = new[] { PinCapability.GP },
Capabilities = new[] { PinCapability.GP, PinCapability.UARTRTS }, Name = "BCM 27"
Name = "BCM 17" });
});
internal static readonly Lazy<GpioPin> Pin03 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin03, 15) {
internal static readonly Lazy<GpioPin> Pin02 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin02, 13) Capabilities = new[] { PinCapability.GP },
{ Name = "BCM 22"
Capabilities = new[] { PinCapability.GP }, });
Name = "BCM 27"
}); internal static readonly Lazy<GpioPin> Pin12 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin12, 19) {
Capabilities = new[] { PinCapability.GP, PinCapability.SPIMOSI },
internal static readonly Lazy<GpioPin> Pin03 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin03, 15) Name = "BCM 10 (MOSI)"
{ });
Capabilities = new[] { PinCapability.GP },
Name = "BCM 22" internal static readonly Lazy<GpioPin> Pin13 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin13, 21) {
}); Capabilities = new[] { PinCapability.GP, PinCapability.SPIMISO },
Name = "BCM 9 (MISO)"
internal static readonly Lazy<GpioPin> Pin12 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin12, 19) });
{
Capabilities = new[] { PinCapability.GP, PinCapability.SPIMOSI }, internal static readonly Lazy<GpioPin> Pin14 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin14, 23) {
Name = "BCM 10 (MOSI)" Capabilities = new[] { PinCapability.GP, PinCapability.SPICLK },
}); Name = "BCM 11 (SCLCK)"
});
internal static readonly Lazy<GpioPin> Pin13 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin13, 21)
{ internal static readonly Lazy<GpioPin> Pin30 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin30, 27) {
Capabilities = new[] { PinCapability.GP, PinCapability.SPIMISO }, Capabilities = new[] { PinCapability.I2CSDA },
Name = "BCM 9 (MISO)" Name = "BCM 0 (ID_SD)"
}); });
internal static readonly Lazy<GpioPin> Pin14 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin14, 23) internal static readonly Lazy<GpioPin> Pin31 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin31, 28) {
{ Capabilities = new[] { PinCapability.I2CSCL },
Capabilities = new[] { PinCapability.GP, PinCapability.SPICLK }, Name = "BCM 1 (ID_SC)"
Name = "BCM 11 (SCLCK)" });
});
internal static readonly Lazy<GpioPin> Pin11 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin11, 26) {
internal static readonly Lazy<GpioPin> Pin30 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin30, 27) Capabilities = new[] { PinCapability.GP, PinCapability.SPICS },
{ Name = "BCM 7 (CE1)"
Capabilities = new[] { PinCapability.I2CSDA }, });
Name = "BCM 0 (ID_SD)"
}); internal static readonly Lazy<GpioPin> Pin10 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin10, 24) {
Capabilities = new[] { PinCapability.GP, PinCapability.SPICS },
internal static readonly Lazy<GpioPin> Pin31 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin31, 28) Name = "BCM 8 (CE0)"
{ });
Capabilities = new[] { PinCapability.I2CSCL },
Name = "BCM 1 (ID_SC)" internal static readonly Lazy<GpioPin> Pin06 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin06, 22) {
}); Capabilities = new[] { PinCapability.GP },
Name = "BCM 25"
internal static readonly Lazy<GpioPin> Pin11 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin11, 26) });
{ internal static readonly Lazy<GpioPin> Pin05 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin05, 18) {
Capabilities = new[] { PinCapability.GP, PinCapability.SPICS }, Capabilities = new[] { PinCapability.GP },
Name = "BCM 7 (CE1)" Name = "BCM 24"
}); });
internal static readonly Lazy<GpioPin> Pin10 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin10, 24) internal static readonly Lazy<GpioPin> Pin04 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin04, 16) {
{ Capabilities = new[] { PinCapability.GP },
Capabilities = new[] { PinCapability.GP, PinCapability.SPICS }, Name = "BCM 23"
Name = "BCM 8 (CE0)" });
});
internal static readonly Lazy<GpioPin> Pin01 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin01, 12) {
internal static readonly Lazy<GpioPin> Pin06 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin06, 22) Capabilities = new[] { PinCapability.GP, PinCapability.PWM },
{ Name = "BCM 18 (PWM0)"
Capabilities = new[] { PinCapability.GP }, });
Name = "BCM 25"
}); internal static readonly Lazy<GpioPin> Pin16 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin16, 10) {
internal static readonly Lazy<GpioPin> Pin05 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin05, 18) Capabilities = new[] { PinCapability.UARTRXD },
{ Name = "BCM 15 (RXD)"
Capabilities = new[] { PinCapability.GP }, });
Name = "BCM 24"
}); internal static readonly Lazy<GpioPin> Pin15 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin15, 8) {
Capabilities = new[] { PinCapability.UARTTXD },
internal static readonly Lazy<GpioPin> Pin04 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin04, 16) Name = "BCM 14 (TXD)"
{ });
Capabilities = new[] { PinCapability.GP },
Name = "BCM 23" internal static readonly Lazy<GpioPin> Pin21 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin21, 29) {
}); Capabilities = new[] { PinCapability.GP },
Name = "BCM 5"
internal static readonly Lazy<GpioPin> Pin01 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin01, 12) });
{
Capabilities = new[] { PinCapability.GP, PinCapability.PWM }, internal static readonly Lazy<GpioPin> Pin22 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin22, 31) {
Name = "BCM 18 (PWM0)" Capabilities = new[] { PinCapability.GP },
}); Name = "BCM 6"
});
internal static readonly Lazy<GpioPin> Pin16 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin16, 10)
{ internal static readonly Lazy<GpioPin> Pin23 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin23, 33) {
Capabilities = new[] { PinCapability.UARTRXD }, Capabilities = new[] { PinCapability.GP, PinCapability.PWM },
Name = "BCM 15 (RXD)" Name = "BCM 13 (PWM1)"
}); });
internal static readonly Lazy<GpioPin> Pin15 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin15, 8) internal static readonly Lazy<GpioPin> Pin24 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin24, 35) {
{ Capabilities = new[] { PinCapability.GP, PinCapability.SPIMISO },
Capabilities = new[] { PinCapability.UARTTXD }, Name = "BCM 19 (MISO)"
Name = "BCM 14 (TXD)" });
});
internal static readonly Lazy<GpioPin> Pin25 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin25, 37) {
internal static readonly Lazy<GpioPin> Pin21 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin21, 29) Capabilities = new[] { PinCapability.GP },
{ Name = "BCM 26"
Capabilities = new[] { PinCapability.GP }, });
Name = "BCM 5"
}); internal static readonly Lazy<GpioPin> Pin29 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin29, 40) {
Capabilities = new[] { PinCapability.GP, PinCapability.SPICLK },
internal static readonly Lazy<GpioPin> Pin22 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin22, 31) Name = "BCM 21 (SCLK)"
{ });
Capabilities = new[] { PinCapability.GP },
Name = "BCM 6" internal static readonly Lazy<GpioPin> Pin28 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin28, 38) {
}); Capabilities = new[] { PinCapability.GP, PinCapability.SPIMOSI },
Name = "BCM 20 (MOSI)"
internal static readonly Lazy<GpioPin> Pin23 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin23, 33) });
{
Capabilities = new[] { PinCapability.GP, PinCapability.PWM }, internal static readonly Lazy<GpioPin> Pin27 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin27, 36) {
Name = "BCM 13 (PWM1)" Capabilities = new[] { PinCapability.GP },
}); Name = "BCM 16"
});
internal static readonly Lazy<GpioPin> Pin24 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin24, 35) internal static readonly Lazy<GpioPin> Pin26 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin26, 32) {
{ Capabilities = new[] { PinCapability.GP },
Capabilities = new[] { PinCapability.GP, PinCapability.SPIMISO }, Name = "BCM 12 (PWM0)"
Name = "BCM 19 (MISO)" });
});
internal static readonly Lazy<GpioPin> Pin17 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin17, 3) {
internal static readonly Lazy<GpioPin> Pin25 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin25, 37) Capabilities = new[] { PinCapability.GP, PinCapability.I2CSDA },
{ Name = "BCM 28 (SDA)"
Capabilities = new[] { PinCapability.GP }, });
Name = "BCM 26"
}); internal static readonly Lazy<GpioPin> Pin18 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin18, 4) {
Capabilities = new[] { PinCapability.GP, PinCapability.I2CSCL },
internal static readonly Lazy<GpioPin> Pin29 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin29, 40) Name = "BCM 29 (SCL)"
{ });
Capabilities = new[] { PinCapability.GP, PinCapability.SPICLK },
Name = "BCM 21 (SCLK)" internal static readonly Lazy<GpioPin> Pin19 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin19, 5) {
}); Capabilities = new[] { PinCapability.GP },
Name = "BCM 30"
internal static readonly Lazy<GpioPin> Pin28 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin28, 38) });
{
Capabilities = new[] { PinCapability.GP, PinCapability.SPIMOSI }, internal static readonly Lazy<GpioPin> Pin20 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin20, 6) {
Name = "BCM 20 (MOSI)" Capabilities = new[] { PinCapability.GP },
}); Name = "BCM 31"
});
internal static readonly Lazy<GpioPin> Pin27 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin27, 36)
{ #endregion
Capabilities = new[] { PinCapability.GP }, }
Name = "BCM 16"
});
internal static readonly Lazy<GpioPin> Pin26 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin26, 32)
{
Capabilities = new[] { PinCapability.GP },
Name = "BCM 12 (PWM0)"
});
internal static readonly Lazy<GpioPin> Pin17 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin17, 3)
{
Capabilities = new[] { PinCapability.GP, PinCapability.I2CSDA },
Name = "BCM 28 (SDA)"
});
internal static readonly Lazy<GpioPin> Pin18 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin18, 4)
{
Capabilities = new[] { PinCapability.GP, PinCapability.I2CSCL },
Name = "BCM 29 (SCL)"
});
internal static readonly Lazy<GpioPin> Pin19 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin19, 5)
{
Capabilities = new[] { PinCapability.GP },
Name = "BCM 30"
});
internal static readonly Lazy<GpioPin> Pin20 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin20, 6)
{
Capabilities = new[] { PinCapability.GP },
Name = "BCM 31"
});
#endregion
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,93 +1,87 @@
namespace Unosquare.RaspberryIO.Gpio using Unosquare.RaspberryIO.Native;
{ using Unosquare.Swan.Abstractions;
using Native; using System.Collections.Generic;
using Swan.Abstractions; using System.Collections.ObjectModel;
using System.Collections.Generic; using System.Linq;
using System.Collections.ObjectModel; using System;
using System.Linq;
namespace Unosquare.RaspberryIO.Gpio {
/// <summary> /// <summary>
/// A simple wrapper for the I2c bus on the Raspberry Pi /// A simple wrapper for the I2c bus on the Raspberry Pi
/// </summary> /// </summary>
public class I2CBus : SingletonBase<I2CBus> public class I2CBus : SingletonBase<I2CBus> {
{ // TODO: It would be nice to integrate i2c device detection.
// TODO: It would be nice to integrate i2c device detection. private static readonly Object SyncRoot = new Object();
private static readonly object SyncRoot = new object(); private readonly Dictionary<Int32, I2CDevice> _devices = new Dictionary<Int32, I2CDevice>();
private readonly Dictionary<int, I2CDevice> _devices = new Dictionary<int, I2CDevice>();
/// <summary>
/// <summary> /// Prevents a default instance of the <see cref="I2CBus"/> class from being created.
/// Prevents a default instance of the <see cref="I2CBus"/> class from being created. /// </summary>
/// </summary> private I2CBus() {
private I2CBus() // placeholder
{ }
// placeholder
} /// <summary>
/// Gets the registered devices as a read only collection.
/// <summary> /// </summary>
/// Gets the registered devices as a read only collection. public ReadOnlyCollection<I2CDevice> Devices => new ReadOnlyCollection<I2CDevice>(this._devices.Values.ToArray());
/// </summary>
public ReadOnlyCollection<I2CDevice> Devices => new ReadOnlyCollection<I2CDevice>(_devices.Values.ToArray()); /// <summary>
/// Gets the <see cref="I2CDevice"/> with the specified device identifier.
/// <summary> /// </summary>
/// Gets the <see cref="I2CDevice"/> with the specified device identifier. /// <value>
/// </summary> /// The <see cref="I2CDevice"/>.
/// <value> /// </value>
/// The <see cref="I2CDevice"/>. /// <param name="deviceId">The device identifier.</param>
/// </value> /// <returns>A reference to an I2C device</returns>
/// <param name="deviceId">The device identifier.</param> public I2CDevice this[Int32 deviceId] => this.GetDeviceById(deviceId);
/// <returns>A reference to an I2C device</returns>
public I2CDevice this[int deviceId] => GetDeviceById(deviceId); /// <summary>
/// Gets the device by identifier.
/// <summary> /// </summary>
/// Gets the device by identifier. /// <param name="deviceId">The device identifier.</param>
/// </summary> /// <returns>The device reference</returns>
/// <param name="deviceId">The device identifier.</param> public I2CDevice GetDeviceById(Int32 deviceId) {
/// <returns>The device reference</returns> lock(SyncRoot) {
public I2CDevice GetDeviceById(int deviceId) return this._devices[deviceId];
{ }
lock (SyncRoot) }
{
return _devices[deviceId]; /// <summary>
} /// Adds a device to the bus by its Id. If the device is already registered it simply returns the existing device.
} /// </summary>
/// <param name="deviceId">The device identifier.</param>
/// <summary> /// <returns>The device reference</returns>
/// Adds a device to the bus by its Id. If the device is already registered it simply returns the existing device. /// <exception cref="KeyNotFoundException">When the device file descriptor is not found</exception>
/// </summary> public I2CDevice AddDevice(Int32 deviceId) {
/// <param name="deviceId">The device identifier.</param> lock(SyncRoot) {
/// <returns>The device reference</returns> if(this._devices.ContainsKey(deviceId)) {
/// <exception cref="KeyNotFoundException">When the device file descriptor is not found</exception> return this._devices[deviceId];
public I2CDevice AddDevice(int deviceId) }
{
lock (SyncRoot) Int32 fileDescriptor = SetupFileDescriptor(deviceId);
{ if(fileDescriptor < 0) {
if (_devices.ContainsKey(deviceId)) throw new KeyNotFoundException($"Device with id {deviceId} could not be registered with the I2C bus. Error Code: {fileDescriptor}.");
return _devices[deviceId]; }
var fileDescriptor = SetupFileDescriptor(deviceId); I2CDevice device = new I2CDevice(deviceId, fileDescriptor);
if (fileDescriptor < 0) this._devices[deviceId] = device;
throw new KeyNotFoundException($"Device with id {deviceId} could not be registered with the I2C bus. Error Code: {fileDescriptor}."); return device;
}
var device = new I2CDevice(deviceId, fileDescriptor); }
_devices[deviceId] = device;
return device; /// <summary>
} /// This initializes the I2C system with your given device identifier.
} /// The ID is the I2C number of the device and you can use the i2cdetect program to find this out.
/// wiringPiI2CSetup() will work out which revision Raspberry Pi you have and open the appropriate device in /dev.
/// <summary> /// The return value is the standard Linux filehandle, or -1 if any error in which case, you can consult errno as usual.
/// This initializes the I2C system with your given device identifier. /// </summary>
/// The ID is the I2C number of the device and you can use the i2cdetect program to find this out. /// <param name="deviceId">The device identifier.</param>
/// wiringPiI2CSetup() will work out which revision Raspberry Pi you have and open the appropriate device in /dev. /// <returns>The Linux file handle</returns>
/// The return value is the standard Linux filehandle, or -1 if any error in which case, you can consult errno as usual. private static Int32 SetupFileDescriptor(Int32 deviceId) {
/// </summary> lock(SyncRoot) {
/// <param name="deviceId">The device identifier.</param> return WiringPi.WiringPiI2CSetup(deviceId);
/// <returns>The Linux file handle</returns> }
private static int SetupFileDescriptor(int deviceId) }
{ }
lock (SyncRoot) }
{
return WiringPi.WiringPiI2CSetup(deviceId);
}
}
}
}

View File

@ -1,195 +1,193 @@
namespace Unosquare.RaspberryIO.Gpio using System;
{ using System.Threading.Tasks;
using System; using Unosquare.RaspberryIO.Native;
using System.Threading.Tasks;
using Native; namespace Unosquare.RaspberryIO.Gpio {
/// <summary>
/// <summary> /// Represents a device on the I2C Bus
/// Represents a device on the I2C Bus /// </summary>
/// </summary> public class I2CDevice {
public class I2CDevice private readonly Object _syncLock = new Object();
{
private readonly object _syncLock = new object(); /// <summary>
/// Initializes a new instance of the <see cref="I2CDevice"/> class.
/// <summary> /// </summary>
/// Initializes a new instance of the <see cref="I2CDevice"/> class. /// <param name="deviceId">The device identifier.</param>
/// </summary> /// <param name="fileDescriptor">The file descriptor.</param>
/// <param name="deviceId">The device identifier.</param> internal I2CDevice(Int32 deviceId, Int32 fileDescriptor) {
/// <param name="fileDescriptor">The file descriptor.</param> this.DeviceId = deviceId;
internal I2CDevice(int deviceId, int fileDescriptor) this.FileDescriptor = fileDescriptor;
{ }
DeviceId = deviceId;
FileDescriptor = fileDescriptor; /// <summary>
} /// Gets the device identifier.
/// </summary>
/// <summary> /// <value>
/// Gets the device identifier. /// The device identifier.
/// </summary> /// </value>
/// <value> public Int32 DeviceId {
/// The device identifier. get;
/// </value> }
public int DeviceId { get; }
/// <summary>
/// <summary> /// Gets the standard POSIX file descriptor.
/// Gets the standard POSIX file descriptor. /// </summary>
/// </summary> /// <value>
/// <value> /// The file descriptor.
/// The file descriptor. /// </value>
/// </value> public Int32 FileDescriptor {
public int FileDescriptor { get; } get;
}
/// <summary>
/// Reads a byte from the specified file descriptor /// <summary>
/// </summary> /// Reads a byte from the specified file descriptor
/// <returns>The byte from device</returns> /// </summary>
public byte Read() /// <returns>The byte from device</returns>
{ public Byte Read() {
lock (_syncLock) lock(this._syncLock) {
{ Int32 result = WiringPi.WiringPiI2CRead(this.FileDescriptor);
var result = WiringPi.WiringPiI2CRead(FileDescriptor); if(result < 0) {
if (result < 0) HardwareException.Throw(nameof(I2CDevice), nameof(Read)); HardwareException.Throw(nameof(I2CDevice), nameof(Read));
return (byte)result; }
}
} return (Byte)result;
}
/// <summary> }
/// Reads a byte from the specified file descriptor
/// </summary> /// <summary>
/// <returns>The byte from device</returns> /// Reads a byte from the specified file descriptor
public Task<byte> ReadAsync() => Task.Run(() => Read()); /// </summary>
/// <returns>The byte from device</returns>
/// <summary> public Task<Byte> ReadAsync() => Task.Run(() => this.Read());
/// Reads a buffer of the specified length, one byte at a time
/// </summary> /// <summary>
/// <param name="length">The length.</param> /// Reads a buffer of the specified length, one byte at a time
/// <returns>The byte array from device</returns> /// </summary>
public byte[] Read(int length) /// <param name="length">The length.</param>
{ /// <returns>The byte array from device</returns>
lock (_syncLock) public Byte[] Read(Int32 length) {
{ lock(this._syncLock) {
var buffer = new byte[length]; Byte[] buffer = new Byte[length];
for (var i = 0; i < length; i++) for(Int32 i = 0; i < length; i++) {
{ Int32 result = WiringPi.WiringPiI2CRead(this.FileDescriptor);
var result = WiringPi.WiringPiI2CRead(FileDescriptor); if(result < 0) {
if (result < 0) HardwareException.Throw(nameof(I2CDevice), nameof(Read)); HardwareException.Throw(nameof(I2CDevice), nameof(Read));
buffer[i] = (byte)result; }
}
buffer[i] = (Byte)result;
return buffer; }
}
} return buffer;
}
/// <summary> }
/// Reads a buffer of the specified length, one byte at a time
/// </summary> /// <summary>
/// <param name="length">The length.</param> /// Reads a buffer of the specified length, one byte at a time
/// <returns>The byte array from device</returns> /// </summary>
public Task<byte[]> ReadAsync(int length) => Task.Run(() => Read(length)); /// <param name="length">The length.</param>
/// <returns>The byte array from device</returns>
/// <summary> public Task<Byte[]> ReadAsync(Int32 length) => Task.Run(() => this.Read(length));
/// Writes a byte of data the specified file descriptor.
/// </summary> /// <summary>
/// <param name="data">The data.</param> /// Writes a byte of data the specified file descriptor.
public void Write(byte data) /// </summary>
{ /// <param name="data">The data.</param>
lock (_syncLock) public void Write(Byte data) {
{ lock(this._syncLock) {
var result = WiringPi.WiringPiI2CWrite(FileDescriptor, data); Int32 result = WiringPi.WiringPiI2CWrite(this.FileDescriptor, data);
if (result < 0) HardwareException.Throw(nameof(I2CDevice), nameof(Write)); if(result < 0) {
} HardwareException.Throw(nameof(I2CDevice), nameof(Write));
} }
}
/// <summary> }
/// Writes a byte of data the specified file descriptor.
/// </summary> /// <summary>
/// <param name="data">The data.</param> /// Writes a byte of data the specified file descriptor.
/// <returns>The awaitable task</returns> /// </summary>
public Task WriteAsync(byte data) => Task.Run(() => { Write(data); }); /// <param name="data">The data.</param>
/// <returns>The awaitable task</returns>
/// <summary> public Task WriteAsync(Byte data) => Task.Run(() => this.Write(data));
/// Writes a set of bytes to the specified file descriptor.
/// </summary> /// <summary>
/// <param name="data">The data.</param> /// Writes a set of bytes to the specified file descriptor.
public void Write(byte[] data) /// </summary>
{ /// <param name="data">The data.</param>
lock (_syncLock) public void Write(Byte[] data) {
{ lock(this._syncLock) {
foreach (var b in data) foreach(Byte b in data) {
{ Int32 result = WiringPi.WiringPiI2CWrite(this.FileDescriptor, b);
var result = WiringPi.WiringPiI2CWrite(FileDescriptor, b); if(result < 0) {
if (result < 0) HardwareException.Throw(nameof(I2CDevice), nameof(Write)); HardwareException.Throw(nameof(I2CDevice), nameof(Write));
} }
} }
} }
}
/// <summary>
/// Writes a set of bytes to the specified file descriptor. /// <summary>
/// </summary> /// Writes a set of bytes to the specified file descriptor.
/// <param name="data">The data.</param> /// </summary>
/// <returns>The awaitable task</returns> /// <param name="data">The data.</param>
public Task WriteAsync(byte[] data) /// <returns>The awaitable task</returns>
{ public Task WriteAsync(Byte[] data) => Task.Run(() => this.Write(data));
return Task.Run(() => { Write(data); });
} /// <summary>
/// These write an 8 or 16-bit data value into the device register indicated.
/// <summary> /// </summary>
/// These write an 8 or 16-bit data value into the device register indicated. /// <param name="address">The register.</param>
/// </summary> /// <param name="data">The data.</param>
/// <param name="address">The register.</param> public void WriteAddressByte(Int32 address, Byte data) {
/// <param name="data">The data.</param> lock(this._syncLock) {
public void WriteAddressByte(int address, byte data) Int32 result = WiringPi.WiringPiI2CWriteReg8(this.FileDescriptor, address, data);
{ if(result < 0) {
lock (_syncLock) HardwareException.Throw(nameof(I2CDevice), nameof(WriteAddressByte));
{ }
var result = WiringPi.WiringPiI2CWriteReg8(FileDescriptor, address, data); }
if (result < 0) HardwareException.Throw(nameof(I2CDevice), nameof(WriteAddressByte)); }
}
} /// <summary>
/// These write an 8 or 16-bit data value into the device register indicated.
/// <summary> /// </summary>
/// These write an 8 or 16-bit data value into the device register indicated. /// <param name="address">The register.</param>
/// </summary> /// <param name="data">The data.</param>
/// <param name="address">The register.</param> public void WriteAddressWord(Int32 address, UInt16 data) {
/// <param name="data">The data.</param> lock(this._syncLock) {
public void WriteAddressWord(int address, ushort data) Int32 result = WiringPi.WiringPiI2CWriteReg16(this.FileDescriptor, address, data);
{ if(result < 0) {
lock (_syncLock) HardwareException.Throw(nameof(I2CDevice), nameof(WriteAddressWord));
{ }
var result = WiringPi.WiringPiI2CWriteReg16(FileDescriptor, address, data); }
if (result < 0) HardwareException.Throw(nameof(I2CDevice), nameof(WriteAddressWord)); }
}
} /// <summary>
/// These read an 8 or 16-bit value from the device register indicated.
/// <summary> /// </summary>
/// These read an 8 or 16-bit value from the device register indicated. /// <param name="address">The register.</param>
/// </summary> /// <returns>The address byte from device</returns>
/// <param name="address">The register.</param> public Byte ReadAddressByte(Int32 address) {
/// <returns>The address byte from device</returns> lock(this._syncLock) {
public byte ReadAddressByte(int address) Int32 result = WiringPi.WiringPiI2CReadReg8(this.FileDescriptor, address);
{ if(result < 0) {
lock (_syncLock) HardwareException.Throw(nameof(I2CDevice), nameof(ReadAddressByte));
{ }
var result = WiringPi.WiringPiI2CReadReg8(FileDescriptor, address);
if (result < 0) HardwareException.Throw(nameof(I2CDevice), nameof(ReadAddressByte)); return (Byte)result;
}
return (byte)result; }
}
} /// <summary>
/// These read an 8 or 16-bit value from the device register indicated.
/// <summary> /// </summary>
/// These read an 8 or 16-bit value from the device register indicated. /// <param name="address">The register.</param>
/// </summary> /// <returns>The address word from device</returns>
/// <param name="address">The register.</param> public UInt16 ReadAddressWord(Int32 address) {
/// <returns>The address word from device</returns> lock(this._syncLock) {
public ushort ReadAddressWord(int address) Int32 result = WiringPi.WiringPiI2CReadReg16(this.FileDescriptor, address);
{ if(result < 0) {
lock (_syncLock) HardwareException.Throw(nameof(I2CDevice), nameof(ReadAddressWord));
{ }
var result = WiringPi.WiringPiI2CReadReg16(FileDescriptor, address);
if (result < 0) HardwareException.Throw(nameof(I2CDevice), nameof(ReadAddressWord)); return Convert.ToUInt16(result);
}
return Convert.ToUInt16(result); }
} }
} }
}
}

View File

@ -1,72 +1,72 @@
namespace Unosquare.RaspberryIO.Gpio using System;
{ using Unosquare.Swan.Abstractions;
using Swan.Abstractions;
namespace Unosquare.RaspberryIO.Gpio {
/// <summary>
/// The SPI Bus containing the 2 SPI channels
/// </summary>
public class SpiBus : SingletonBase<SpiBus> {
/// <summary> /// <summary>
/// The SPI Bus containing the 2 SPI channels /// Prevents a default instance of the <see cref="SpiBus"/> class from being created.
/// </summary> /// </summary>
public class SpiBus : SingletonBase<SpiBus> private SpiBus() {
{ // placeholder
/// <summary> }
/// Prevents a default instance of the <see cref="SpiBus"/> class from being created.
/// </summary> #region SPI Access
private SpiBus()
{ /// <summary>
// placeholder /// Gets or sets the channel 0 frequency in Hz.
} /// </summary>
/// <value>
#region SPI Access /// The channel0 frequency.
/// </value>
/// <summary> public Int32 Channel0Frequency {
/// Gets or sets the channel 0 frequency in Hz. get; set;
/// </summary> }
/// <value>
/// The channel0 frequency. /// <summary>
/// </value> /// Gets the SPI bus on channel 1.
public int Channel0Frequency { get; set; } /// </summary>
/// <value>
/// <summary> /// The channel0.
/// Gets the SPI bus on channel 1. /// </value>
/// </summary> public SpiChannel Channel0 {
/// <value> get {
/// The channel0. if(this.Channel0Frequency == 0) {
/// </value> this.Channel0Frequency = SpiChannel.DefaultFrequency;
public SpiChannel Channel0 }
{
get return SpiChannel.Retrieve(SpiChannelNumber.Channel0, this.Channel0Frequency);
{ }
if (Channel0Frequency == 0) }
Channel0Frequency = SpiChannel.DefaultFrequency;
/// <summary>
return SpiChannel.Retrieve(SpiChannelNumber.Channel0, Channel0Frequency); /// Gets or sets the channel 1 frequency in Hz
} /// </summary>
} /// <value>
/// The channel1 frequency.
/// <summary> /// </value>
/// Gets or sets the channel 1 frequency in Hz public Int32 Channel1Frequency {
/// </summary> get; set;
/// <value> }
/// The channel1 frequency.
/// </value> /// <summary>
public int Channel1Frequency { get; set; } /// Gets the SPI bus on channel 1.
/// </summary>
/// <summary> /// <value>
/// Gets the SPI bus on channel 1. /// The channel1.
/// </summary> /// </value>
/// <value> public SpiChannel Channel1 {
/// The channel1. get {
/// </value> if(this.Channel1Frequency == 0) {
public SpiChannel Channel1 this.Channel1Frequency = SpiChannel.DefaultFrequency;
{ }
get
{ return SpiChannel.Retrieve(SpiChannelNumber.Channel1, this.Channel1Frequency);
if (Channel1Frequency == 0) }
Channel1Frequency = SpiChannel.DefaultFrequency; }
return SpiChannel.Retrieve(SpiChannelNumber.Channel1, Channel1Frequency); #endregion
} }
}
#endregion
}
} }

View File

@ -1,154 +1,154 @@
namespace Unosquare.RaspberryIO.Gpio using Unosquare.RaspberryIO.Native;
{ using Unosquare.Swan;
using Native; using System;
using Swan; using System.Collections.Generic;
using System; using System.Threading.Tasks;
using System.Collections.Generic;
using System.Threading.Tasks; namespace Unosquare.RaspberryIO.Gpio {
/// <summary>
/// Provides access to using the SPI buses on the GPIO.
/// SPI is a bus that works like a ring shift register
/// The number of bytes pushed is equal to the number of bytes received.
/// </summary>
public sealed class SpiChannel {
/// <summary> /// <summary>
/// Provides access to using the SPI buses on the GPIO. /// The minimum frequency of an SPI Channel
/// SPI is a bus that works like a ring shift register
/// The number of bytes pushed is equal to the number of bytes received.
/// </summary> /// </summary>
public sealed class SpiChannel public const Int32 MinFrequency = 500000;
{
/// <summary> /// <summary>
/// The minimum frequency of an SPI Channel /// The maximum frequency of an SPI channel
/// </summary> /// </summary>
public const int MinFrequency = 500000; public const Int32 MaxFrequency = 32000000;
/// <summary> /// <summary>
/// The maximum frequency of an SPI channel /// The default frequency of SPI channels
/// </summary> /// This is set to 8 Mhz wich is typical in modern hardware.
public const int MaxFrequency = 32000000; /// </summary>
public const Int32 DefaultFrequency = 8000000;
/// <summary>
/// The default frequency of SPI channels private static readonly Object SyncRoot = new Object();
/// This is set to 8 Mhz wich is typical in modern hardware. private static readonly Dictionary<SpiChannelNumber, SpiChannel> Buses = new Dictionary<SpiChannelNumber, SpiChannel>();
/// </summary> private readonly Object _syncLock = new Object();
public const int DefaultFrequency = 8000000;
/// <summary>
private static readonly object SyncRoot = new object(); /// Initializes a new instance of the <see cref="SpiChannel"/> class.
private static readonly Dictionary<SpiChannelNumber, SpiChannel> Buses = new Dictionary<SpiChannelNumber, SpiChannel>(); /// </summary>
private readonly object _syncLock = new object(); /// <param name="channel">The channel.</param>
/// <param name="frequency">The frequency.</param>
/// <summary> private SpiChannel(SpiChannelNumber channel, Int32 frequency) {
/// Initializes a new instance of the <see cref="SpiChannel"/> class. lock(SyncRoot) {
/// </summary> this.Frequency = frequency.Clamp(MinFrequency, MaxFrequency);
/// <param name="channel">The channel.</param> this.Channel = (Int32)channel;
/// <param name="frequency">The frequency.</param> this.FileDescriptor = WiringPi.WiringPiSPISetup((Int32)channel, this.Frequency);
private SpiChannel(SpiChannelNumber channel, int frequency)
{ if(this.FileDescriptor < 0) {
lock (SyncRoot) HardwareException.Throw(nameof(SpiChannel), channel.ToString());
{ }
Frequency = frequency.Clamp(MinFrequency, MaxFrequency); }
Channel = (int)channel; }
FileDescriptor = WiringPi.WiringPiSPISetup((int)channel, Frequency);
/// <summary>
if (FileDescriptor < 0) /// Gets the standard initialization file descriptor.
{ /// anything negative means error.
HardwareException.Throw(nameof(SpiChannel), channel.ToString()); /// </summary>
} /// <value>
} /// The file descriptor.
} /// </value>
public Int32 FileDescriptor {
/// <summary> get;
/// Gets the standard initialization file descriptor. }
/// anything negative means error.
/// </summary> /// <summary>
/// <value> /// Gets the channel.
/// The file descriptor. /// </summary>
/// </value> public Int32 Channel {
public int FileDescriptor { get; } get;
}
/// <summary>
/// Gets the channel. /// <summary>
/// </summary> /// Gets the frequency.
public int Channel { get; } /// </summary>
public Int32 Frequency {
/// <summary> get;
/// Gets the frequency. }
/// </summary>
public int Frequency { get; } /// <summary>
/// Sends data and simultaneously receives the data in the return buffer
/// <summary> /// </summary>
/// Sends data and simultaneously receives the data in the return buffer /// <param name="buffer">The buffer.</param>
/// </summary> /// <returns>The read bytes from the ring-style bus</returns>
/// <param name="buffer">The buffer.</param> public Byte[] SendReceive(Byte[] buffer) {
/// <returns>The read bytes from the ring-style bus</returns> if(buffer == null || buffer.Length == 0) {
public byte[] SendReceive(byte[] buffer) return null;
{ }
if (buffer == null || buffer.Length == 0)
return null; lock(this._syncLock) {
Byte[] spiBuffer = new Byte[buffer.Length];
lock (_syncLock) Array.Copy(buffer, spiBuffer, buffer.Length);
{
var spiBuffer = new byte[buffer.Length]; Int32 result = WiringPi.WiringPiSPIDataRW(this.Channel, spiBuffer, spiBuffer.Length);
Array.Copy(buffer, spiBuffer, buffer.Length); if(result < 0) {
HardwareException.Throw(nameof(SpiChannel), nameof(SendReceive));
var result = WiringPi.WiringPiSPIDataRW(Channel, spiBuffer, spiBuffer.Length); }
if (result < 0) HardwareException.Throw(nameof(SpiChannel), nameof(SendReceive));
return spiBuffer;
return spiBuffer; }
} }
}
/// <summary>
/// <summary> /// Sends data and simultaneously receives the data in the return buffer
/// Sends data and simultaneously receives the data in the return buffer /// </summary>
/// </summary> /// <param name="buffer">The buffer.</param>
/// <param name="buffer">The buffer.</param> /// <returns>
/// <returns> /// The read bytes from the ring-style bus
/// The read bytes from the ring-style bus /// </returns>
/// </returns> public Task<Byte[]> SendReceiveAsync(Byte[] buffer) => Task.Run(() => this.SendReceive(buffer));
public Task<byte[]> SendReceiveAsync(byte[] buffer) => Task.Run(() => SendReceive(buffer));
/// <summary>
/// <summary> /// Writes the specified buffer the the underlying FileDescriptor.
/// Writes the specified buffer the the underlying FileDescriptor. /// Do not use this method if you expect data back.
/// Do not use this method if you expect data back. /// This method is efficient if used in a fire-and-forget scenario
/// This method is efficient if used in a fire-and-forget scenario /// like sending data over to those long RGB LED strips
/// like sending data over to those long RGB LED strips /// </summary>
/// </summary> /// <param name="buffer">The buffer.</param>
/// <param name="buffer">The buffer.</param> public void Write(Byte[] buffer) {
public void Write(byte[] buffer) lock(this._syncLock) {
{ Int32 result = Standard.Write(this.FileDescriptor, buffer, buffer.Length);
lock (_syncLock)
{ if(result < 0) {
var result = Standard.Write(FileDescriptor, buffer, buffer.Length); HardwareException.Throw(nameof(SpiChannel), nameof(Write));
}
if (result < 0) }
HardwareException.Throw(nameof(SpiChannel), nameof(Write)); }
}
} /// <summary>
/// Writes the specified buffer the the underlying FileDescriptor.
/// <summary> /// Do not use this method if you expect data back.
/// Writes the specified buffer the the underlying FileDescriptor. /// This method is efficient if used in a fire-and-forget scenario
/// Do not use this method if you expect data back. /// like sending data over to those long RGB LED strips
/// This method is efficient if used in a fire-and-forget scenario /// </summary>
/// like sending data over to those long RGB LED strips /// <param name="buffer">The buffer.</param>
/// </summary> /// <returns>The awaitable task</returns>
/// <param name="buffer">The buffer.</param> public Task WriteAsync(Byte[] buffer) => Task.Run(() => this.Write(buffer));
/// <returns>The awaitable task</returns>
public Task WriteAsync(byte[] buffer) => Task.Run(() => { Write(buffer); }); /// <summary>
/// Retrieves the spi bus. If the bus channel is not registered it sets it up automatically.
/// <summary> /// If it had been previously registered, then the bus is simply returned.
/// Retrieves the spi bus. If the bus channel is not registered it sets it up automatically. /// </summary>
/// If it had been previously registered, then the bus is simply returned. /// <param name="channel">The channel.</param>
/// </summary> /// <param name="frequency">The frequency.</param>
/// <param name="channel">The channel.</param> /// <returns>The usable SPI channel</returns>
/// <param name="frequency">The frequency.</param> internal static SpiChannel Retrieve(SpiChannelNumber channel, Int32 frequency) {
/// <returns>The usable SPI channel</returns> lock(SyncRoot) {
internal static SpiChannel Retrieve(SpiChannelNumber channel, int frequency) if(Buses.ContainsKey(channel)) {
{ return Buses[channel];
lock (SyncRoot) }
{
if (Buses.ContainsKey(channel)) SpiChannel newBus = new SpiChannel(channel, frequency);
return Buses[channel]; Buses[channel] = newBus;
return newBus;
var newBus = new SpiChannel(channel, frequency); }
Buses[channel] = newBus; }
return newBus; }
}
}
}
} }

View File

@ -1,12 +1,11 @@
namespace Unosquare.RaspberryIO.Native namespace Unosquare.RaspberryIO.Native {
{ /// <summary>
/// <summary> /// A delegate defining a callback for an Interrupt Service Routine
/// A delegate defining a callback for an Interrupt Service Routine /// </summary>
/// </summary> public delegate void InterruptServiceRoutineCallback();
public delegate void InterruptServiceRoutineCallback();
/// <summary>
/// <summary> /// Defines the body of a thread worker
/// Defines the body of a thread worker /// </summary>
/// </summary> public delegate void ThreadWorker();
public delegate void ThreadWorker();
} }

View File

@ -1,73 +1,73 @@
namespace Unosquare.RaspberryIO.Native using Unosquare.Swan;
{ using System;
using Swan; using System.Runtime.InteropServices;
using System;
using System.Runtime.InteropServices; namespace Unosquare.RaspberryIO.Native {
/// <summary>
/// Represents a low-level exception, typically thrown when return codes from a
/// low-level operation is non-zero or in some cases when it is less than zero.
/// </summary>
/// <seealso cref="Exception" />
public class HardwareException : Exception {
/// <summary> /// <summary>
/// Represents a low-level exception, typically thrown when return codes from a /// Initializes a new instance of the <see cref="HardwareException" /> class.
/// low-level operation is non-zero or in some cases when it is less than zero.
/// </summary> /// </summary>
/// <seealso cref="Exception" /> /// <param name="errorCode">The error code.</param>
public class HardwareException : Exception /// <param name="component">The component.</param>
{ public HardwareException(Int32 errorCode, String component)
/// <summary> : base($"A hardware exception occurred. Error Code: {errorCode}") {
/// Initializes a new instance of the <see cref="HardwareException" /> class. this.ExtendedMessage = null;
/// </summary>
/// <param name="errorCode">The error code.</param> try {
/// <param name="component">The component.</param> this.ExtendedMessage = Standard.Strerror(errorCode);
public HardwareException(int errorCode, string component) } catch {
: base($"A hardware exception occurred. Error Code: {errorCode}") // TODO: strerror not working great...
{ $"Could not retrieve native error description using {nameof(Standard.Strerror)}".Error(Pi.LoggerSource);
ExtendedMessage = null; }
try this.ErrorCode = errorCode;
{ this.Component = component;
ExtendedMessage = Standard.Strerror(errorCode); }
}
catch /// <summary>
{ /// Gets the error code.
// TODO: strerror not working great... /// </summary>
$"Could not retrieve native error description using {nameof(Standard.Strerror)}".Error(Pi.LoggerSource); /// <value>
} /// The error code.
/// </value>
ErrorCode = errorCode; public Int32 ErrorCode {
Component = component; get;
} }
/// <summary> /// <summary>
/// Gets the error code. /// Gets the component.
/// </summary> /// </summary>
/// <value> /// <value>
/// The error code. /// The component.
/// </value> /// </value>
public int ErrorCode { get; } public String Component {
get;
/// <summary> }
/// Gets the component.
/// </summary> /// <summary>
/// <value> /// Gets the extended message (could be null).
/// The component. /// </summary>
/// </value> /// <value>
public string Component { get; } /// The extended message.
/// </value>
/// <summary> public String ExtendedMessage {
/// Gets the extended message (could be null). get;
/// </summary> }
/// <value>
/// The extended message. /// <summary>
/// </value> /// Throws a new instance of a hardware error by retrieving the last error number (errno).
public string ExtendedMessage { get; } /// </summary>
/// <param name="className">Name of the class.</param>
/// <summary> /// <param name="methodName">Name of the method.</param>
/// Throws a new instance of a hardware error by retrieving the last error number (errno). /// <exception cref="HardwareException">When an error thrown by an API call occurs</exception>
/// </summary> public static void Throw(String className, String methodName) => throw new HardwareException(Marshal.GetLastWin32Error(), $"{className}.{methodName}");
/// <param name="className">Name of the class.</param>
/// <param name="methodName">Name of the method.</param> /// <inheritdoc />
/// <exception cref="HardwareException">When an error thrown by an API call occurs</exception> public override String ToString() => $"{this.GetType()}{(String.IsNullOrWhiteSpace(this.Component) ? String.Empty : $" on {this.Component}")}: ({this.ErrorCode}) - {this.Message}";
public static void Throw(string className, string methodName) => throw new HardwareException(Marshal.GetLastWin32Error(), $"{className}.{methodName}"); }
/// <inheritdoc />
public override string ToString() => $"{GetType()}{(string.IsNullOrWhiteSpace(Component) ? string.Empty : $" on {Component}")}: ({ErrorCode}) - {Message}";
}
} }

View File

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

View File

@ -1,84 +1,80 @@
namespace Unosquare.RaspberryIO.Native using Unosquare.Swan;
{ using System;
using Swan; using System.Runtime.InteropServices;
using System; using System.Text;
using System.Runtime.InteropServices;
using System.Text; namespace Unosquare.RaspberryIO.Native {
/// <summary>
/// Provides standard libc calls using platform-invoke
/// </summary>
internal static class Standard {
internal const String LibCLibrary = "libc";
#region LibC Calls
/// <summary> /// <summary>
/// Provides standard libc calls using platform-invoke /// Strerrors the specified error.
/// </summary> /// </summary>
internal static class Standard /// <param name="error">The error.</param>
{ /// <returns></returns>
internal const string LibCLibrary = "libc"; public static String Strerror(Int32 error) {
if(!Runtime.IsUsingMonoRuntime) {
#region LibC Calls return StrError(error);
}
/// <summary>
/// Strerrors the specified error. try {
/// </summary> StringBuilder buffer = new StringBuilder(256);
/// <param name="error">The error.</param> Int32 result = Strerror(error, buffer, (UInt64)buffer.Capacity);
/// <returns></returns> return (result != -1) ? buffer.ToString() : null;
public static string Strerror(int error) } catch(EntryPointNotFoundException) {
{ return null;
if (!Runtime.IsUsingMonoRuntime) return StrError(error); }
}
try
{ /// <summary>
var buffer = new StringBuilder(256); /// Changes file permissions on a Unix file system
var result = Strerror(error, buffer, (ulong)buffer.Capacity); /// </summary>
return (result != -1) ? buffer.ToString() : null; /// <param name="filename">The filename.</param>
} /// <param name="mode">The mode.</param>
catch (EntryPointNotFoundException) /// <returns>The result</returns>
{ [DllImport(LibCLibrary, EntryPoint = "chmod", SetLastError = true)]
return null; public static extern Int32 Chmod(String filename, UInt32 mode);
}
} /// <summary>
/// Converts a string to a 32 bit integer. Use endpointer as IntPtr.Zero
/// <summary> /// </summary>
/// Changes file permissions on a Unix file system /// <param name="numberString">The number string.</param>
/// </summary> /// <param name="endPointer">The end pointer.</param>
/// <param name="filename">The filename.</param> /// <param name="numberBase">The number base.</param>
/// <param name="mode">The mode.</param> /// <returns>The result</returns>
/// <returns>The result</returns> [DllImport(LibCLibrary, EntryPoint = "strtol", SetLastError = true)]
[DllImport(LibCLibrary, EntryPoint = "chmod", SetLastError = true)] public static extern Int32 StringToInteger(String numberString, IntPtr endPointer, Int32 numberBase);
public static extern int Chmod(string filename, uint mode);
/// <summary>
/// <summary> /// The write() function attempts to write nbytes from buffer to the file associated with handle. On text files, it expands each LF to a CR/LF.
/// Converts a string to a 32 bit integer. Use endpointer as IntPtr.Zero /// The function returns the number of bytes written to the file. A return value of -1 indicates an error, with errno set appropriately.
/// </summary> /// </summary>
/// <param name="numberString">The number string.</param> /// <param name="fd">The fd.</param>
/// <param name="endPointer">The end pointer.</param> /// <param name="buffer">The buffer.</param>
/// <param name="numberBase">The number base.</param> /// <param name="count">The count.</param>
/// <returns>The result</returns> /// <returns>The result</returns>
[DllImport(LibCLibrary, EntryPoint = "strtol", SetLastError = true)] [DllImport(LibCLibrary, EntryPoint = "write", SetLastError = true)]
public static extern int StringToInteger(string numberString, IntPtr endPointer, int numberBase); public static extern Int32 Write(Int32 fd, Byte[] buffer, Int32 count);
/// <summary> /// <summary>
/// The write() function attempts to write nbytes from buffer to the file associated with handle. On text files, it expands each LF to a CR/LF. /// Fills in the structure with information about the system.
/// The function returns the number of bytes written to the file. A return value of -1 indicates an error, with errno set appropriately. /// </summary>
/// </summary> /// <param name="name">The name.</param>
/// <param name="fd">The fd.</param> /// <returns>The result</returns>
/// <param name="buffer">The buffer.</param> [DllImport(LibCLibrary, EntryPoint = "uname", SetLastError = true)]
/// <param name="count">The count.</param> public static extern Int32 Uname(out SystemName name);
/// <returns>The result</returns>
[DllImport(LibCLibrary, EntryPoint = "write", SetLastError = true)] [DllImport(LibCLibrary, EntryPoint = "strerror", SetLastError = true)]
public static extern int Write(int fd, byte[] buffer, int count); private static extern String StrError(Int32 errnum);
/// <summary> [DllImport("MonoPosixHelper", EntryPoint = "Mono_Posix_Syscall_strerror_r", SetLastError = true)]
/// Fills in the structure with information about the system. private static extern Int32 Strerror(Int32 error, [Out] StringBuilder buffer, UInt64 length);
/// </summary>
/// <param name="name">The name.</param> #endregion
/// <returns>The result</returns> }
[DllImport(LibCLibrary, EntryPoint = "uname", SetLastError = true)]
public static extern int Uname(out SystemName name);
[DllImport(LibCLibrary, EntryPoint = "strerror", SetLastError = true)]
private static extern string StrError(int errnum);
[DllImport("MonoPosixHelper", EntryPoint = "Mono_Posix_Syscall_strerror_r", SetLastError = true)]
private static extern int Strerror(int error, [Out] StringBuilder buffer, ulong length);
#endregion
}
} }

View File

@ -1,47 +1,46 @@
namespace Unosquare.RaspberryIO.Native using System;
{ using System.Runtime.InteropServices;
using System.Runtime.InteropServices;
namespace Unosquare.RaspberryIO.Native {
/// <summary>
/// OS uname structure
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
internal struct SystemName {
/// <summary> /// <summary>
/// OS uname structure /// System name
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
internal struct SystemName public String SysName;
{
/// <summary> /// <summary>
/// System name /// Node name
/// </summary> /// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
public string SysName; public String NodeName;
/// <summary> /// <summary>
/// Node name /// Release level
/// </summary> /// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
public string NodeName; public String Release;
/// <summary> /// <summary>
/// Release level /// Version level
/// </summary> /// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
public string Release; public String Version;
/// <summary> /// <summary>
/// Version level /// Hardware level
/// </summary> /// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
public string Version; public String Machine;
/// <summary> /// <summary>
/// Hardware level /// Domain name
/// </summary> /// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
public string Machine; public String DomainName;
}
/// <summary>
/// Domain name
/// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
public string DomainName;
}
} }

View File

@ -1,28 +1,26 @@
namespace Unosquare.RaspberryIO.Native namespace Unosquare.RaspberryIO.Native {
{ /// <summary>
/// Defines the different threading locking keys
/// </summary>
public enum ThreadLockKey {
/// <summary> /// <summary>
/// Defines the different threading locking keys /// The lock 0
/// </summary> /// </summary>
public enum ThreadLockKey Lock0 = 0,
{
/// <summary> /// <summary>
/// The lock 0 /// The lock 1
/// </summary> /// </summary>
Lock0 = 0, Lock1 = 1,
/// <summary> /// <summary>
/// The lock 1 /// The lock 2
/// </summary> /// </summary>
Lock1 = 1, Lock2 = 2,
/// <summary> /// <summary>
/// The lock 2 /// The lock 3
/// </summary> /// </summary>
Lock2 = 2, Lock3 = 3,
}
/// <summary>
/// The lock 3
/// </summary>
Lock3 = 3,
}
} }

View File

@ -1,108 +1,108 @@
namespace Unosquare.RaspberryIO.Native using Unosquare.Swan;
{ using Unosquare.Swan.Abstractions;
using Swan; using System;
using Swan.Abstractions;
using System; namespace Unosquare.RaspberryIO.Native {
/// <summary>
/// Provides access to timing and threading properties and methods
/// </summary>
public class Timing : SingletonBase<Timing> {
/// <summary> /// <summary>
/// Provides access to timing and threading properties and methods /// Prevents a default instance of the <see cref="Timing"/> class from being created.
/// </summary> /// </summary>
public class Timing : SingletonBase<Timing> /// <exception cref="NotSupportedException">Could not initialize the GPIO controller</exception>
{ private Timing() {
/// <summary> // placeholder
/// Prevents a default instance of the <see cref="Timing"/> class from being created. }
/// </summary>
/// <exception cref="NotSupportedException">Could not initialize the GPIO controller</exception> /// <summary>
private Timing() /// This returns a number representing the number of milliseconds since your program
{ /// initialized the GPIO controller.
// placeholder /// It returns an unsigned 32-bit number which wraps after 49 days.
} /// </summary>
/// <value>
/// <summary> /// The milliseconds since setup.
/// This returns a number representing the number of milliseconds since your program /// </value>
/// initialized the GPIO controller. public UInt32 MillisecondsSinceSetup => WiringPi.Millis();
/// It returns an unsigned 32-bit number which wraps after 49 days.
/// </summary> /// <summary>
/// <value> /// This returns a number representing the number of microseconds since your
/// The milliseconds since setup. /// program initialized the GPIO controller
/// </value> /// It returns an unsigned 32-bit number which wraps after approximately 71 minutes.
public uint MillisecondsSinceSetup => WiringPi.Millis(); /// </summary>
/// <value>
/// <summary> /// The microseconds since setup.
/// This returns a number representing the number of microseconds since your /// </value>
/// program initialized the GPIO controller public UInt32 MicrosecondsSinceSetup => WiringPi.Micros();
/// It returns an unsigned 32-bit number which wraps after approximately 71 minutes.
/// </summary> /// <summary>
/// <value> /// This causes program execution to pause for at least howLong milliseconds.
/// The microseconds since setup. /// Due to the multi-tasking nature of Linux it could be longer.
/// </value> /// Note that the maximum delay is an unsigned 32-bit integer or approximately 49 days.
public uint MicrosecondsSinceSetup => WiringPi.Micros(); /// </summary>
/// <param name="value">The value.</param>
/// <summary> public static void SleepMilliseconds(UInt32 value) => WiringPi.Delay(value);
/// This causes program execution to pause for at least howLong milliseconds.
/// Due to the multi-tasking nature of Linux it could be longer. /// <summary>
/// Note that the maximum delay is an unsigned 32-bit integer or approximately 49 days. /// This causes program execution to pause for at least howLong microseconds.
/// </summary> /// Due to the multi-tasking nature of Linux it could be longer.
/// <param name="value">The value.</param> /// Note that the maximum delay is an unsigned 32-bit integer microseconds or approximately 71 minutes.
public static void SleepMilliseconds(uint value) => WiringPi.Delay(value); /// Delays under 100 microseconds are timed using a hard-coded loop continually polling the system time,
/// Delays over 100 microseconds are done using the system nanosleep() function
/// <summary> /// You may need to consider the implications of very short delays on the overall performance of the system,
/// This causes program execution to pause for at least howLong microseconds. /// especially if using threads.
/// Due to the multi-tasking nature of Linux it could be longer. /// </summary>
/// Note that the maximum delay is an unsigned 32-bit integer microseconds or approximately 71 minutes. /// <param name="value">The value.</param>
/// Delays under 100 microseconds are timed using a hard-coded loop continually polling the system time, public void SleepMicroseconds(UInt32 value) => WiringPi.DelayMicroseconds(value);
/// Delays over 100 microseconds are done using the system nanosleep() function
/// You may need to consider the implications of very short delays on the overall performance of the system, /// <summary>
/// especially if using threads. /// This attempts to shift your program (or thread in a multi-threaded program) to a higher priority and
/// </summary> /// enables a real-time scheduling. The priority parameter should be from 0 (the default) to 99 (the maximum).
/// <param name="value">The value.</param> /// This wont make your program go any faster, but it will give it a bigger slice of time when other programs
public void SleepMicroseconds(uint value) => WiringPi.DelayMicroseconds(value); /// are running. The priority parameter works relative to others so you can make one program priority 1 and
/// another priority 2 and it will have the same effect as setting one to 10 and the other to 90
/// <summary> /// (as long as no other programs are running with elevated priorities)
/// This attempts to shift your program (or thread in a multi-threaded program) to a higher priority and /// </summary>
/// enables a real-time scheduling. The priority parameter should be from 0 (the default) to 99 (the maximum). /// <param name="priority">The priority.</param>
/// This wont make your program go any faster, but it will give it a bigger slice of time when other programs public void SetThreadPriority(Int32 priority) {
/// are running. The priority parameter works relative to others so you can make one program priority 1 and priority = priority.Clamp(0, 99);
/// another priority 2 and it will have the same effect as setting one to 10 and the other to 90 Int32 result = WiringPi.PiHiPri(priority);
/// (as long as no other programs are running with elevated priorities) if(result < 0) {
/// </summary> HardwareException.Throw(nameof(Timing), nameof(SetThreadPriority));
/// <param name="priority">The priority.</param> }
public void SetThreadPriority(int priority) }
{
priority = priority.Clamp(0, 99); /// <summary>
var result = WiringPi.PiHiPri(priority); /// This is really nothing more than a simplified interface to the Posix threads mechanism that Linux supports.
if (result < 0) HardwareException.Throw(nameof(Timing), nameof(SetThreadPriority)); /// See the manual pages on Posix threads (man pthread) if you need more control over them.
} /// </summary>
/// <param name="worker">The worker.</param>
/// <summary> /// <exception cref="ArgumentNullException">worker</exception>
/// This is really nothing more than a simplified interface to the Posix threads mechanism that Linux supports. public void CreateThread(ThreadWorker worker) {
/// See the manual pages on Posix threads (man pthread) if you need more control over them. if(worker == null) {
/// </summary> throw new ArgumentNullException(nameof(worker));
/// <param name="worker">The worker.</param> }
/// <exception cref="ArgumentNullException">worker</exception>
public void CreateThread(ThreadWorker worker) Int32 result = WiringPi.PiThreadCreate(worker);
{ if(result != 0) {
if (worker == null) HardwareException.Throw(nameof(Timing), nameof(CreateThread));
throw new ArgumentNullException(nameof(worker)); }
}
var result = WiringPi.PiThreadCreate(worker);
if (result != 0) HardwareException.Throw(nameof(Timing), nameof(CreateThread)); /// <summary>
} /// These allow you to synchronize variable updates from your main program to any threads running in your program.
/// keyNum is a number from 0 to 3 and represents a “key”. When another process tries to lock the same key,
/// <summary> /// it will be stalled until the first process has unlocked the same key.
/// These allow you to synchronize variable updates from your main program to any threads running in your program. /// </summary>
/// keyNum is a number from 0 to 3 and represents a “key”. When another process tries to lock the same key, /// <param name="key">The key.</param>
/// it will be stalled until the first process has unlocked the same key. public void Lock(ThreadLockKey key) => WiringPi.PiLock((Int32)key);
/// </summary>
/// <param name="key">The key.</param> /// <summary>
public void Lock(ThreadLockKey key) => WiringPi.PiLock((int)key); /// These allow you to synchronize variable updates from your main program to any threads running in your program.
/// keyNum is a number from 0 to 3 and represents a “key”. When another process tries to lock the same key,
/// <summary> /// it will be stalled until the first process has unlocked the same key.
/// These allow you to synchronize variable updates from your main program to any threads running in your program. /// </summary>
/// keyNum is a number from 0 to 3 and represents a “key”. When another process tries to lock the same key, /// <param name="key">The key.</param>
/// it will be stalled until the first process has unlocked the same key. public void Unlock(ThreadLockKey key) => WiringPi.PiUnlock((Int32)key);
/// </summary> }
/// <param name="key">The key.</param>
public void Unlock(ThreadLockKey key) => WiringPi.PiUnlock((int)key);
}
} }

View File

@ -1,79 +1,78 @@
namespace Unosquare.RaspberryIO.Native using System;
{ using System.Runtime.InteropServices;
using System.Runtime.InteropServices;
namespace Unosquare.RaspberryIO.Native {
public partial class WiringPi public partial class WiringPi {
{ #region WiringPi - I2C Library Calls
#region WiringPi - I2C Library Calls
/// <summary>
/// <summary> /// Simple device read. Some devices present data when you read them without having to do any register transactions.
/// Simple device read. Some devices present data when you read them without having to do any register transactions. /// </summary>
/// </summary> /// <param name="fd">The fd.</param>
/// <param name="fd">The fd.</param> /// <returns>The result</returns>
/// <returns>The result</returns> [DllImport(WiringPiLibrary, EntryPoint = "wiringPiI2CRead", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiI2CRead", SetLastError = true)] public static extern Int32 WiringPiI2CRead(Int32 fd);
public static extern int WiringPiI2CRead(int fd);
/// <summary>
/// <summary> /// These read an 8-bit value from the device register indicated.
/// These read an 8-bit value from the device register indicated. /// </summary>
/// </summary> /// <param name="fd">The fd.</param>
/// <param name="fd">The fd.</param> /// <param name="reg">The reg.</param>
/// <param name="reg">The reg.</param> /// <returns>The result</returns>
/// <returns>The result</returns> [DllImport(WiringPiLibrary, EntryPoint = "wiringPiI2CReadReg8", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiI2CReadReg8", SetLastError = true)] public static extern Int32 WiringPiI2CReadReg8(Int32 fd, Int32 reg);
public static extern int WiringPiI2CReadReg8(int fd, int reg);
/// <summary>
/// <summary> /// These read a 16-bit value from the device register indicated.
/// These read a 16-bit value from the device register indicated. /// </summary>
/// </summary> /// <param name="fd">The fd.</param>
/// <param name="fd">The fd.</param> /// <param name="reg">The reg.</param>
/// <param name="reg">The reg.</param> /// <returns>The result</returns>
/// <returns>The result</returns> [DllImport(WiringPiLibrary, EntryPoint = "wiringPiI2CReadReg16", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiI2CReadReg16", SetLastError = true)] public static extern Int32 WiringPiI2CReadReg16(Int32 fd, Int32 reg);
public static extern int WiringPiI2CReadReg16(int fd, int reg);
/// <summary>
/// <summary> /// Simple device write. Some devices accept data this way without needing to access any internal registers.
/// Simple device write. Some devices accept data this way without needing to access any internal registers. /// </summary>
/// </summary> /// <param name="fd">The fd.</param>
/// <param name="fd">The fd.</param> /// <param name="data">The data.</param>
/// <param name="data">The data.</param> /// <returns>The result</returns>
/// <returns>The result</returns> [DllImport(WiringPiLibrary, EntryPoint = "wiringPiI2CWrite", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiI2CWrite", SetLastError = true)] public static extern Int32 WiringPiI2CWrite(Int32 fd, Int32 data);
public static extern int WiringPiI2CWrite(int fd, int data);
/// <summary>
/// <summary> /// These write an 8-bit data value into the device register indicated.
/// These write an 8-bit data value into the device register indicated. /// </summary>
/// </summary> /// <param name="fd">The fd.</param>
/// <param name="fd">The fd.</param> /// <param name="reg">The reg.</param>
/// <param name="reg">The reg.</param> /// <param name="data">The data.</param>
/// <param name="data">The data.</param> /// <returns>The result</returns>
/// <returns>The result</returns> [DllImport(WiringPiLibrary, EntryPoint = "wiringPiI2CWriteReg8", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiI2CWriteReg8", SetLastError = true)] public static extern Int32 WiringPiI2CWriteReg8(Int32 fd, Int32 reg, Int32 data);
public static extern int WiringPiI2CWriteReg8(int fd, int reg, int data);
/// <summary>
/// <summary> /// These write a 16-bit data value into the device register indicated.
/// These write a 16-bit data value into the device register indicated. /// </summary>
/// </summary> /// <param name="fd">The fd.</param>
/// <param name="fd">The fd.</param> /// <param name="reg">The reg.</param>
/// <param name="reg">The reg.</param> /// <param name="data">The data.</param>
/// <param name="data">The data.</param> /// <returns>The result</returns>
/// <returns>The result</returns> [DllImport(WiringPiLibrary, EntryPoint = "wiringPiI2CWriteReg16", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiI2CWriteReg16", SetLastError = true)] public static extern Int32 WiringPiI2CWriteReg16(Int32 fd, Int32 reg, Int32 data);
public static extern int WiringPiI2CWriteReg16(int fd, int reg, int data);
/// <summary>
/// <summary> /// This initialises the I2C system with your given device identifier.
/// This initialises the I2C system with your given device identifier. /// The ID is the I2C number of the device and you can use the i2cdetect program to find this out. wiringPiI2CSetup()
/// The ID is the I2C number of the device and you can use the i2cdetect program to find this out. wiringPiI2CSetup() /// will work out which revision Raspberry Pi you have and open the appropriate device in /dev.
/// will work out which revision Raspberry Pi you have and open the appropriate device in /dev. /// The return value is the standard Linux filehandle, or -1 if any error in which case, you can consult errno as usual.
/// The return value is the standard Linux filehandle, or -1 if any error in which case, you can consult errno as usual. /// E.g. the popular MCP23017 GPIO expander is usually device Id 0x20, so this is the number you would pass into wiringPiI2CSetup().
/// E.g. the popular MCP23017 GPIO expander is usually device Id 0x20, so this is the number you would pass into wiringPiI2CSetup(). /// </summary>
/// </summary> /// <param name="devId">The dev identifier.</param>
/// <param name="devId">The dev identifier.</param> /// <returns>The result</returns>
/// <returns>The result</returns> [DllImport(WiringPiLibrary, EntryPoint = "wiringPiI2CSetup", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiI2CSetup", SetLastError = true)] public static extern Int32 WiringPiI2CSetup(Int32 devId);
public static extern int WiringPiI2CSetup(int devId);
#endregion
#endregion
}
}
} }

View File

@ -1,73 +1,72 @@
namespace Unosquare.RaspberryIO.Native using System;
{ using System.Runtime.InteropServices;
using System.Runtime.InteropServices;
namespace Unosquare.RaspberryIO.Native {
public partial class WiringPi public partial class WiringPi {
{ #region WiringPi - Serial Port
#region WiringPi - Serial Port
/// <summary>
/// <summary> /// This opens and initialises the serial device and sets the baud rate. It sets the port into “raw” mode (character at a time and no translations),
/// This opens and initialises the serial device and sets the baud rate. It sets the port into “raw” mode (character at a time and no translations), /// and sets the read timeout to 10 seconds. The return value is the file descriptor or -1 for any error, in which case errno will be set as appropriate.
/// and sets the read timeout to 10 seconds. The return value is the file descriptor or -1 for any error, in which case errno will be set as appropriate. /// The wiringSerial library is intended to provide simplified control suitable for most applications, however if you need advanced control
/// The wiringSerial library is intended to provide simplified control suitable for most applications, however if you need advanced control /// e.g. parity control, modem control lines (via a USB adapter, there are none on the Pis on-board UART!) and so on,
/// e.g. parity control, modem control lines (via a USB adapter, there are none on the Pis on-board UART!) and so on, /// then you need to do some of this the old fashioned way.
/// then you need to do some of this the old fashioned way. /// </summary>
/// </summary> /// <param name="device">The device.</param>
/// <param name="device">The device.</param> /// <param name="baud">The baud.</param>
/// <param name="baud">The baud.</param> /// <returns>The result</returns>
/// <returns>The result</returns> [DllImport(WiringPiLibrary, EntryPoint = "serialOpen", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "serialOpen", SetLastError = true)] public static extern Int32 SerialOpen(String device, Int32 baud);
public static extern int SerialOpen(string device, int baud);
/// <summary>
/// <summary> /// Closes the device identified by the file descriptor given.
/// Closes the device identified by the file descriptor given. /// </summary>
/// </summary> /// <param name="fd">The fd.</param>
/// <param name="fd">The fd.</param> /// <returns>The result</returns>
/// <returns>The result</returns> [DllImport(WiringPiLibrary, EntryPoint = "serialClose", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "serialClose", SetLastError = true)] public static extern Int32 SerialClose(Int32 fd);
public static extern int SerialClose(int fd);
/// <summary>
/// <summary> /// Sends the single byte to the serial device identified by the given file descriptor.
/// Sends the single byte to the serial device identified by the given file descriptor. /// </summary>
/// </summary> /// <param name="fd">The fd.</param>
/// <param name="fd">The fd.</param> /// <param name="c">The c.</param>
/// <param name="c">The c.</param> [DllImport(WiringPiLibrary, EntryPoint = "serialPutchar", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "serialPutchar", SetLastError = true)] public static extern void SerialPutchar(Int32 fd, Byte c);
public static extern void SerialPutchar(int fd, byte c);
/// <summary>
/// <summary> /// Sends the nul-terminated string to the serial device identified by the given file descriptor.
/// Sends the nul-terminated string to the serial device identified by the given file descriptor. /// </summary>
/// </summary> /// <param name="fd">The fd.</param>
/// <param name="fd">The fd.</param> /// <param name="s">The s.</param>
/// <param name="s">The s.</param> [DllImport(WiringPiLibrary, EntryPoint = "serialPuts", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "serialPuts", SetLastError = true)] public static extern void SerialPuts(Int32 fd, String s);
public static extern void SerialPuts(int fd, string s);
/// <summary>
/// <summary> /// Returns the number of characters available for reading, or -1 for any error condition,
/// Returns the number of characters available for reading, or -1 for any error condition, /// in which case errno will be set appropriately.
/// in which case errno will be set appropriately. /// </summary>
/// </summary> /// <param name="fd">The fd.</param>
/// <param name="fd">The fd.</param> /// <returns>The result</returns>
/// <returns>The result</returns> [DllImport(WiringPiLibrary, EntryPoint = "serialDataAvail", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "serialDataAvail", SetLastError = true)] public static extern Int32 SerialDataAvail(Int32 fd);
public static extern int SerialDataAvail(int fd);
/// <summary>
/// <summary> /// Returns the next character available on the serial device.
/// Returns the next character available on the serial device. /// This call will block for up to 10 seconds if no data is available (when it will return -1)
/// This call will block for up to 10 seconds if no data is available (when it will return -1) /// </summary>
/// </summary> /// <param name="fd">The fd.</param>
/// <param name="fd">The fd.</param> /// <returns>The result</returns>
/// <returns>The result</returns> [DllImport(WiringPiLibrary, EntryPoint = "serialGetchar", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "serialGetchar", SetLastError = true)] public static extern Int32 SerialGetchar(Int32 fd);
public static extern int SerialGetchar(int fd);
/// <summary>
/// <summary> /// This discards all data received, or waiting to be send down the given device.
/// This discards all data received, or waiting to be send down the given device. /// </summary>
/// </summary> /// <param name="fd">The fd.</param>
/// <param name="fd">The fd.</param> [DllImport(WiringPiLibrary, EntryPoint = "serialFlush", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "serialFlush", SetLastError = true)] public static extern void SerialFlush(Int32 fd);
public static extern void SerialFlush(int fd);
#endregion
#endregion }
}
} }

View File

@ -1,36 +1,35 @@
namespace Unosquare.RaspberryIO.Native using System;
{ using System.Runtime.InteropServices;
using System.Runtime.InteropServices;
namespace Unosquare.RaspberryIO.Native {
public partial class WiringPi public partial class WiringPi {
{ #region WiringPi - Shift Library
#region WiringPi - Shift Library
/// <summary>
/// <summary> /// This shifts an 8-bit data value in with the data appearing on the dPin and the clock being sent out on the cPin.
/// This shifts an 8-bit data value in with the data appearing on the dPin and the clock being sent out on the cPin. /// Order is either LSBFIRST or MSBFIRST. The data is sampled after the cPin goes high.
/// Order is either LSBFIRST or MSBFIRST. The data is sampled after the cPin goes high. /// (So cPin high, sample data, cPin low, repeat for 8 bits) The 8-bit value is returned by the function.
/// (So cPin high, sample data, cPin low, repeat for 8 bits) The 8-bit value is returned by the function. /// </summary>
/// </summary> /// <param name="dPin">The d pin.</param>
/// <param name="dPin">The d pin.</param> /// <param name="cPin">The c pin.</param>
/// <param name="cPin">The c pin.</param> /// <param name="order">The order.</param>
/// <param name="order">The order.</param> /// <returns>The result</returns>
/// <returns>The result</returns> [DllImport(WiringPiLibrary, EntryPoint = "shiftIn", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "shiftIn", SetLastError = true)] public static extern Byte ShiftIn(Byte dPin, Byte cPin, Byte order);
public static extern byte ShiftIn(byte dPin, byte cPin, byte order);
/// <summary>
/// <summary> /// The shifts an 8-bit data value val out with the data being sent out on dPin and the clock being sent out on the cPin.
/// The shifts an 8-bit data value val out with the data being sent out on dPin and the clock being sent out on the cPin. /// order is as above. Data is clocked out on the rising or falling edge ie. dPin is set, then cPin is taken high then low
/// order is as above. Data is clocked out on the rising or falling edge ie. dPin is set, then cPin is taken high then low /// repeated for the 8 bits.
/// repeated for the 8 bits. /// </summary>
/// </summary> /// <param name="dPin">The d pin.</param>
/// <param name="dPin">The d pin.</param> /// <param name="cPin">The c pin.</param>
/// <param name="cPin">The c pin.</param> /// <param name="order">The order.</param>
/// <param name="order">The order.</param> /// <param name="val">The value.</param>
/// <param name="val">The value.</param> [DllImport(WiringPiLibrary, EntryPoint = "shiftOut", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "shiftOut", SetLastError = true)] public static extern void ShiftOut(Byte dPin, Byte cPin, Byte order, Byte val);
public static extern void ShiftOut(byte dPin, byte cPin, byte order, byte val);
#endregion
#endregion
}
}
} }

View File

@ -1,64 +1,63 @@
namespace Unosquare.RaspberryIO.Native using System;
{ using System.Runtime.InteropServices;
using System.Runtime.InteropServices;
namespace Unosquare.RaspberryIO.Native {
public partial class WiringPi public partial class WiringPi {
{ #region WiringPi - Soft PWM (https://github.com/WiringPi/WiringPi/blob/master/wiringPi/softPwm.h)
#region WiringPi - Soft PWM (https://github.com/WiringPi/WiringPi/blob/master/wiringPi/softPwm.h)
/// <summary>
/// <summary> /// This creates a software controlled PWM pin. You can use any GPIO pin and the pin numbering will be that of the wiringPiSetup()
/// This creates a software controlled PWM pin. You can use any GPIO pin and the pin numbering will be that of the wiringPiSetup() /// function you used. Use 100 for the pwmRange, then the value can be anything from 0 (off) to 100 (fully on) for the given pin.
/// function you used. Use 100 for the pwmRange, then the value can be anything from 0 (off) to 100 (fully on) for the given pin. /// The return value is 0 for success. Anything else and you should check the global errno variable to see what went wrong.
/// The return value is 0 for success. Anything else and you should check the global errno variable to see what went wrong. /// </summary>
/// </summary> /// <param name="pin">The pin.</param>
/// <param name="pin">The pin.</param> /// <param name="initialValue">The initial value.</param>
/// <param name="initialValue">The initial value.</param> /// <param name="pwmRange">The PWM range.</param>
/// <param name="pwmRange">The PWM range.</param> /// <returns>The result</returns>
/// <returns>The result</returns> [DllImport(WiringPiLibrary, EntryPoint = "softPwmCreate", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "softPwmCreate", SetLastError = true)] public static extern Int32 SoftPwmCreate(Int32 pin, Int32 initialValue, Int32 pwmRange);
public static extern int SoftPwmCreate(int pin, int initialValue, int pwmRange);
/// <summary>
/// <summary> /// This updates the PWM value on the given pin. The value is checked to be in-range and pins that havent previously
/// This updates the PWM value on the given pin. The value is checked to be in-range and pins that havent previously /// been initialized via softPwmCreate will be silently ignored.
/// been initialized via softPwmCreate will be silently ignored. /// </summary>
/// </summary> /// <param name="pin">The pin.</param>
/// <param name="pin">The pin.</param> /// <param name="value">The value.</param>
/// <param name="value">The value.</param> [DllImport(WiringPiLibrary, EntryPoint = "softPwmWrite", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "softPwmWrite", SetLastError = true)] public static extern void SoftPwmWrite(Int32 pin, Int32 value);
public static extern void SoftPwmWrite(int pin, int value);
/// <summary>
/// <summary> /// This function is undocumented
/// This function is undocumented /// </summary>
/// </summary> /// <param name="pin">The pin.</param>
/// <param name="pin">The pin.</param> [DllImport(WiringPiLibrary, EntryPoint = "softPwmStop", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "softPwmStop", SetLastError = true)] public static extern void SoftPwmStop(Int32 pin);
public static extern void SoftPwmStop(int pin);
/// <summary>
/// <summary> /// This creates a software controlled tone pin. You can use any GPIO pin and the pin numbering will be that of the wiringPiSetup() function you used.
/// This creates a software controlled tone pin. You can use any GPIO pin and the pin numbering will be that of the wiringPiSetup() function you used. /// The return value is 0 for success. Anything else and you should check the global errno variable to see what went wrong.
/// The return value is 0 for success. Anything else and you should check the global errno variable to see what went wrong. /// </summary>
/// </summary> /// <param name="pin">The pin.</param>
/// <param name="pin">The pin.</param> /// <returns>The result</returns>
/// <returns>The result</returns> [DllImport(WiringPiLibrary, EntryPoint = "softToneCreate", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "softToneCreate", SetLastError = true)] public static extern Int32 SoftToneCreate(Int32 pin);
public static extern int SoftToneCreate(int pin);
/// <summary>
/// <summary> /// This function is undocumented
/// This function is undocumented /// </summary>
/// </summary> /// <param name="pin">The pin.</param>
/// <param name="pin">The pin.</param> [DllImport(WiringPiLibrary, EntryPoint = "softToneStop", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "softToneStop", SetLastError = true)] public static extern void SoftToneStop(Int32 pin);
public static extern void SoftToneStop(int pin);
/// <summary>
/// <summary> /// This updates the tone frequency value on the given pin. The tone will be played until you set the frequency to 0.
/// This updates the tone frequency value on the given pin. The tone will be played until you set the frequency to 0. /// </summary>
/// </summary> /// <param name="pin">The pin.</param>
/// <param name="pin">The pin.</param> /// <param name="freq">The freq.</param>
/// <param name="freq">The freq.</param> [DllImport(WiringPiLibrary, EntryPoint = "softToneWrite", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "softToneWrite", SetLastError = true)] public static extern void SoftToneWrite(Int32 pin, Int32 freq);
public static extern void SoftToneWrite(int pin, int freq);
#endregion
#endregion
}
}
} }

View File

@ -1,53 +1,52 @@
namespace Unosquare.RaspberryIO.Native using System;
{ using System.Runtime.InteropServices;
using System.Runtime.InteropServices;
namespace Unosquare.RaspberryIO.Native {
public partial class WiringPi public partial class WiringPi {
{ #region WiringPi - SPI Library Calls
#region WiringPi - SPI Library Calls
/// <summary>
/// <summary> /// This function is undocumented
/// This function is undocumented /// </summary>
/// </summary> /// <param name="channel">The channel.</param>
/// <param name="channel">The channel.</param> /// <returns>Unknown</returns>
/// <returns>Unknown</returns> [DllImport(WiringPiLibrary, EntryPoint = "wiringPiSPIGetFd", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiSPIGetFd", SetLastError = true)] public static extern Int32 WiringPiSPIGetFd(Int32 channel);
public static extern int WiringPiSPIGetFd(int channel);
/// <summary>
/// <summary> /// This performs a simultaneous write/read transaction over the selected SPI bus. Data that was in your buffer is overwritten by data returned from the SPI bus.
/// This performs a simultaneous write/read transaction over the selected SPI bus. Data that was in your buffer is overwritten by data returned from the SPI bus. /// Thats all there is in the helper library. It is possible to do simple read and writes over the SPI bus using the standard read() and write() system calls though
/// Thats all there is in the helper library. It is possible to do simple read and writes over the SPI bus using the standard read() and write() system calls though /// write() may be better to use for sending data to chains of shift registers, or those LED strings where you send RGB triplets of data.
/// write() may be better to use for sending data to chains of shift registers, or those LED strings where you send RGB triplets of data. /// Devices such as A/D and D/A converters usually need to perform a concurrent write/read transaction to work.
/// Devices such as A/D and D/A converters usually need to perform a concurrent write/read transaction to work. /// </summary>
/// </summary> /// <param name="channel">The channel.</param>
/// <param name="channel">The channel.</param> /// <param name="data">The data.</param>
/// <param name="data">The data.</param> /// <param name="len">The length.</param>
/// <param name="len">The length.</param> /// <returns>The result</returns>
/// <returns>The result</returns> [DllImport(WiringPiLibrary, EntryPoint = "wiringPiSPIDataRW", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiSPIDataRW", SetLastError = true)] public static extern Int32 WiringPiSPIDataRW(Int32 channel, Byte[] data, Int32 len);
public static extern int WiringPiSPIDataRW(int channel, byte[] data, int len);
/// <summary>
/// <summary> /// This function is undocumented
/// This function is undocumented /// </summary>
/// </summary> /// <param name="channel">The channel.</param>
/// <param name="channel">The channel.</param> /// <param name="speed">The speed.</param>
/// <param name="speed">The speed.</param> /// <param name="mode">The mode.</param>
/// <param name="mode">The mode.</param> /// <returns>Unkown</returns>
/// <returns>Unkown</returns> [DllImport(WiringPiLibrary, EntryPoint = "wiringPiSPISetupMode", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiSPISetupMode", SetLastError = true)] public static extern Int32 WiringPiSPISetupMode(Int32 channel, Int32 speed, Int32 mode);
public static extern int WiringPiSPISetupMode(int channel, int speed, int mode);
/// <summary>
/// <summary> /// This is the way to initialize a channel (The Pi has 2 channels; 0 and 1). The speed parameter is an integer
/// This is the way to initialize a channel (The Pi has 2 channels; 0 and 1). The speed parameter is an integer /// in the range 500,000 through 32,000,000 and represents the SPI clock speed in Hz.
/// in the range 500,000 through 32,000,000 and represents the SPI clock speed in Hz. /// The returned value is the Linux file-descriptor for the device, or -1 on error. If an error has happened, you may use the standard errno global variable to see why.
/// The returned value is the Linux file-descriptor for the device, or -1 on error. If an error has happened, you may use the standard errno global variable to see why. /// </summary>
/// </summary> /// <param name="channel">The channel.</param>
/// <param name="channel">The channel.</param> /// <param name="speed">The speed.</param>
/// <param name="speed">The speed.</param> /// <returns>The Linux file descriptor for the device or -1 for error</returns>
/// <returns>The Linux file descriptor for the device or -1 for error</returns> [DllImport(WiringPiLibrary, EntryPoint = "wiringPiSPISetup", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiSPISetup", SetLastError = true)] public static extern Int32 WiringPiSPISetup(Int32 channel, Int32 speed);
public static extern int WiringPiSPISetup(int channel, int speed);
#endregion
#endregion }
}
} }

View File

@ -1,394 +1,392 @@
namespace Unosquare.RaspberryIO.Native using System;
{ using System.Runtime.InteropServices;
using System;
using System.Runtime.InteropServices; namespace Unosquare.RaspberryIO.Native {
/// <summary>
/// Provides native C WiringPi Library function call wrappers
/// All credit for the native library goes to the author of http://wiringpi.com/
/// The wrappers were written based on https://github.com/WiringPi/WiringPi/blob/master/wiringPi/wiringPi.h
/// </summary>
public partial class WiringPi {
internal const String WiringPiLibrary = "libwiringPi.so.2.46";
#region WiringPi - Core Functions (https://github.com/WiringPi/WiringPi/blob/master/wiringPi/wiringPi.h)
/// <summary> /// <summary>
/// Provides native C WiringPi Library function call wrappers /// This initialises wiringPi and assumes that the calling program is going to be using the wiringPi pin numbering scheme.
/// All credit for the native library goes to the author of http://wiringpi.com/ /// This is a simplified numbering scheme which provides a mapping from virtual pin numbers 0 through 16 to the real underlying Broadcom GPIO pin numbers.
/// The wrappers were written based on https://github.com/WiringPi/WiringPi/blob/master/wiringPi/wiringPi.h /// See the pins page for a table which maps the wiringPi pin number to the Broadcom GPIO pin number to the physical location on the edge connector.
/// This function needs to be called with root privileges.
/// </summary> /// </summary>
public partial class WiringPi /// <returns>The result code</returns>
{ [DllImport(WiringPiLibrary, EntryPoint = "wiringPiSetup", SetLastError = true)]
internal const string WiringPiLibrary = "libwiringPi.so.2.46"; public static extern Int32 WiringPiSetup();
#region WiringPi - Core Functions (https://github.com/WiringPi/WiringPi/blob/master/wiringPi/wiringPi.h) /// <summary>
/// This initialises wiringPi but uses the /sys/class/gpio interface rather than accessing the hardware directly.
/// <summary> /// This can be called as a non-root user provided the GPIO pins have been exported before-hand using the gpio program.
/// This initialises wiringPi and assumes that the calling program is going to be using the wiringPi pin numbering scheme. /// Pin numbering in this mode is the native Broadcom GPIO numbers the same as wiringPiSetupGpio() above,
/// This is a simplified numbering scheme which provides a mapping from virtual pin numbers 0 through 16 to the real underlying Broadcom GPIO pin numbers. /// so be aware of the differences between Rev 1 and Rev 2 boards.
/// See the pins page for a table which maps the wiringPi pin number to the Broadcom GPIO pin number to the physical location on the edge connector. ///
/// This function needs to be called with root privileges. /// Note: In this mode you can only use the pins which have been exported via the /sys/class/gpio interface before you run your program.
/// </summary> /// You can do this in a separate shell-script, or by using the system() function from inside your program to call the gpio program.
/// <returns>The result code</returns> /// Also note that some functions have no effect when using this mode as theyre not currently possible to action unless called with root privileges.
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiSetup", SetLastError = true)] /// (although you can use system() to call gpio to set/change modes if needed)
public static extern int WiringPiSetup(); /// </summary>
/// <returns>The result code</returns>
/// <summary> [DllImport(WiringPiLibrary, EntryPoint = "wiringPiSetupSys", SetLastError = true)]
/// This initialises wiringPi but uses the /sys/class/gpio interface rather than accessing the hardware directly. public static extern Int32 WiringPiSetupSys();
/// This can be called as a non-root user provided the GPIO pins have been exported before-hand using the gpio program.
/// Pin numbering in this mode is the native Broadcom GPIO numbers the same as wiringPiSetupGpio() above, /// <summary>
/// so be aware of the differences between Rev 1 and Rev 2 boards. /// This is identical to wiringPiSetup, however it allows the calling programs to use the Broadcom GPIO
/// /// pin numbers directly with no re-mapping.
/// Note: In this mode you can only use the pins which have been exported via the /sys/class/gpio interface before you run your program. /// As above, this function needs to be called with root privileges, and note that some pins are different
/// You can do this in a separate shell-script, or by using the system() function from inside your program to call the gpio program. /// from revision 1 to revision 2 boards.
/// Also note that some functions have no effect when using this mode as theyre not currently possible to action unless called with root privileges. /// </summary>
/// (although you can use system() to call gpio to set/change modes if needed) /// <returns>The result code</returns>
/// </summary> [DllImport(WiringPiLibrary, EntryPoint = "wiringPiSetupGpio", SetLastError = true)]
/// <returns>The result code</returns> public static extern Int32 WiringPiSetupGpio();
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiSetupSys", SetLastError = true)]
public static extern int WiringPiSetupSys(); /// <summary>
/// Identical to wiringPiSetup, however it allows the calling programs to use the physical pin numbers on the P1 connector only.
/// <summary> /// This function needs to be called with root privileges.
/// This is identical to wiringPiSetup, however it allows the calling programs to use the Broadcom GPIO /// </summary>
/// pin numbers directly with no re-mapping. /// <returns>The result code</returns>
/// As above, this function needs to be called with root privileges, and note that some pins are different [DllImport(WiringPiLibrary, EntryPoint = "wiringPiSetupPhys", SetLastError = true)]
/// from revision 1 to revision 2 boards. public static extern Int32 WiringPiSetupPhys();
/// </summary>
/// <returns>The result code</returns> /// <summary>
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiSetupGpio", SetLastError = true)] /// This function is undocumented
public static extern int WiringPiSetupGpio(); /// </summary>
/// <param name="pin">The pin.</param>
/// <summary> /// <param name="mode">The mode.</param>
/// Identical to wiringPiSetup, however it allows the calling programs to use the physical pin numbers on the P1 connector only. [DllImport(WiringPiLibrary, EntryPoint = "pinModeAlt", SetLastError = true)]
/// This function needs to be called with root privileges. public static extern void PinModeAlt(Int32 pin, Int32 mode);
/// </summary>
/// <returns>The result code</returns> /// <summary>
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiSetupPhys", SetLastError = true)] /// This sets the mode of a pin to either INPUT, OUTPUT, PWM_OUTPUT or GPIO_CLOCK.
public static extern int WiringPiSetupPhys(); /// Note that only wiringPi pin 1 (BCM_GPIO 18) supports PWM output and only wiringPi pin 7 (BCM_GPIO 4)
/// supports CLOCK output modes.
/// <summary> ///
/// This function is undocumented /// This function has no effect when in Sys mode. If you need to change the pin mode, then you can
/// </summary> /// do it with the gpio program in a script before you start your program.
/// <param name="pin">The pin.</param> /// </summary>
/// <param name="mode">The mode.</param> /// <param name="pin">The pin.</param>
[DllImport(WiringPiLibrary, EntryPoint = "pinModeAlt", SetLastError = true)] /// <param name="mode">The mode.</param>
public static extern void PinModeAlt(int pin, int mode); [DllImport(WiringPiLibrary, EntryPoint = "pinMode", SetLastError = true)]
public static extern void PinMode(Int32 pin, Int32 mode);
/// <summary>
/// This sets the mode of a pin to either INPUT, OUTPUT, PWM_OUTPUT or GPIO_CLOCK. /// <summary>
/// Note that only wiringPi pin 1 (BCM_GPIO 18) supports PWM output and only wiringPi pin 7 (BCM_GPIO 4) /// This sets the pull-up or pull-down resistor mode on the given pin, which should be set as an input.
/// supports CLOCK output modes. /// Unlike the Arduino, the BCM2835 has both pull-up an down internal resistors. The parameter pud should be; PUD_OFF,
/// /// (no pull up/down), PUD_DOWN (pull to ground) or PUD_UP (pull to 3.3v) The internal pull up/down resistors
/// This function has no effect when in Sys mode. If you need to change the pin mode, then you can /// have a value of approximately 50KΩ on the Raspberry Pi.
/// do it with the gpio program in a script before you start your program. ///
/// </summary> /// This function has no effect on the Raspberry Pis GPIO pins when in Sys mode.
/// <param name="pin">The pin.</param> /// If you need to activate a pull-up/pull-down, then you can do it with the gpio program in a script before you start your program.
/// <param name="mode">The mode.</param> /// </summary>
[DllImport(WiringPiLibrary, EntryPoint = "pinMode", SetLastError = true)] /// <param name="pin">The pin.</param>
public static extern void PinMode(int pin, int mode); /// <param name="pud">The pud.</param>
[DllImport(WiringPiLibrary, EntryPoint = "pullUpDnControl", SetLastError = true)]
/// <summary> public static extern void PullUpDnControl(Int32 pin, Int32 pud);
/// This sets the pull-up or pull-down resistor mode on the given pin, which should be set as an input.
/// Unlike the Arduino, the BCM2835 has both pull-up an down internal resistors. The parameter pud should be; PUD_OFF, /// <summary>
/// (no pull up/down), PUD_DOWN (pull to ground) or PUD_UP (pull to 3.3v) The internal pull up/down resistors /// This function returns the value read at the given pin. It will be HIGH or LOW (1 or 0) depending on the logic level at the pin.
/// have a value of approximately 50KΩ on the Raspberry Pi. /// </summary>
/// /// <param name="pin">The pin.</param>
/// This function has no effect on the Raspberry Pis GPIO pins when in Sys mode. /// <returns>The result code</returns>
/// If you need to activate a pull-up/pull-down, then you can do it with the gpio program in a script before you start your program. [DllImport(WiringPiLibrary, EntryPoint = "digitalRead", SetLastError = true)]
/// </summary> public static extern Int32 DigitalRead(Int32 pin);
/// <param name="pin">The pin.</param>
/// <param name="pud">The pud.</param> /// <summary>
[DllImport(WiringPiLibrary, EntryPoint = "pullUpDnControl", SetLastError = true)] /// Writes the value HIGH or LOW (1 or 0) to the given pin which must have been previously set as an output.
public static extern void PullUpDnControl(int pin, int pud); /// WiringPi treats any non-zero number as HIGH, however 0 is the only representation of LOW.
/// </summary>
/// <summary> /// <param name="pin">The pin.</param>
/// This function returns the value read at the given pin. It will be HIGH or LOW (1 or 0) depending on the logic level at the pin. /// <param name="value">The value.</param>
/// </summary> [DllImport(WiringPiLibrary, EntryPoint = "digitalWrite", SetLastError = true)]
/// <param name="pin">The pin.</param> public static extern void DigitalWrite(Int32 pin, Int32 value);
/// <returns>The result code</returns>
[DllImport(WiringPiLibrary, EntryPoint = "digitalRead", SetLastError = true)] /// <summary>
public static extern int DigitalRead(int pin); /// Writes the value to the PWM register for the given pin. The Raspberry Pi has one
/// on-board PWM pin, pin 1 (BMC_GPIO 18, Phys 12) and the range is 0-1024.
/// <summary> /// Other PWM devices may have other PWM ranges.
/// Writes the value HIGH or LOW (1 or 0) to the given pin which must have been previously set as an output. /// This function is not able to control the Pis on-board PWM when in Sys mode.
/// WiringPi treats any non-zero number as HIGH, however 0 is the only representation of LOW. /// </summary>
/// </summary> /// <param name="pin">The pin.</param>
/// <param name="pin">The pin.</param> /// <param name="value">The value.</param>
/// <param name="value">The value.</param> [DllImport(WiringPiLibrary, EntryPoint = "pwmWrite", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "digitalWrite", SetLastError = true)] public static extern void PwmWrite(Int32 pin, Int32 value);
public static extern void DigitalWrite(int pin, int value);
/// <summary>
/// <summary> /// This returns the value read on the supplied analog input pin. You will need to
/// Writes the value to the PWM register for the given pin. The Raspberry Pi has one /// register additional analog modules to enable this function for devices such as the Gertboard, quick2Wire analog board, etc.
/// on-board PWM pin, pin 1 (BMC_GPIO 18, Phys 12) and the range is 0-1024. /// </summary>
/// Other PWM devices may have other PWM ranges. /// <param name="pin">The pin.</param>
/// This function is not able to control the Pis on-board PWM when in Sys mode. /// <returns>The result code</returns>
/// </summary> [DllImport(WiringPiLibrary, EntryPoint = "analogRead", SetLastError = true)]
/// <param name="pin">The pin.</param> public static extern Int32 AnalogRead(Int32 pin);
/// <param name="value">The value.</param>
[DllImport(WiringPiLibrary, EntryPoint = "pwmWrite", SetLastError = true)] /// <summary>
public static extern void PwmWrite(int pin, int value); /// This writes the given value to the supplied analog pin. You will need to register additional
/// analog modules to enable this function for devices such as the Gertboard.
/// <summary> /// </summary>
/// This returns the value read on the supplied analog input pin. You will need to /// <param name="pin">The pin.</param>
/// register additional analog modules to enable this function for devices such as the Gertboard, quick2Wire analog board, etc. /// <param name="value">The value.</param>
/// </summary> [DllImport(WiringPiLibrary, EntryPoint = "analogWrite", SetLastError = true)]
/// <param name="pin">The pin.</param> public static extern void AnalogWrite(Int32 pin, Int32 value);
/// <returns>The result code</returns>
[DllImport(WiringPiLibrary, EntryPoint = "analogRead", SetLastError = true)] /// <summary>
public static extern int AnalogRead(int pin); /// This returns the board revision of the Raspberry Pi. It will be either 1 or 2. Some of the BCM_GPIO pins changed number and
/// function when moving from board revision 1 to 2, so if you are using BCM_GPIO pin numbers, then you need to be aware of the differences.
/// <summary> /// </summary>
/// This writes the given value to the supplied analog pin. You will need to register additional /// <returns>The result code</returns>
/// analog modules to enable this function for devices such as the Gertboard. [DllImport(WiringPiLibrary, EntryPoint = "piBoardRev", SetLastError = true)]
/// </summary> public static extern Int32 PiBoardRev();
/// <param name="pin">The pin.</param>
/// <param name="value">The value.</param> /// <summary>
[DllImport(WiringPiLibrary, EntryPoint = "analogWrite", SetLastError = true)] /// This function is undocumented
public static extern void AnalogWrite(int pin, int value); /// </summary>
/// <param name="model">The model.</param>
/// <summary> /// <param name="mem">The memory.</param>
/// This returns the board revision of the Raspberry Pi. It will be either 1 or 2. Some of the BCM_GPIO pins changed number and /// <param name="maker">The maker.</param>
/// function when moving from board revision 1 to 2, so if you are using BCM_GPIO pin numbers, then you need to be aware of the differences. /// <param name="overVolted">The over volted.</param>
/// </summary> /// <returns>The result code</returns>
/// <returns>The result code</returns> [DllImport(WiringPiLibrary, EntryPoint = "piBoardId", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "piBoardRev", SetLastError = true)] public static extern Int32 PiBoardId(ref Int32 model, ref Int32 mem, ref Int32 maker, ref Int32 overVolted);
public static extern int PiBoardRev();
/// <summary>
/// <summary> /// This returns the BCM_GPIO pin number of the supplied wiringPi pin. It takes the board revision into account.
/// This function is undocumented /// </summary>
/// </summary> /// <param name="wPiPin">The w pi pin.</param>
/// <param name="model">The model.</param> /// <returns>The result code</returns>
/// <param name="mem">The memory.</param> [DllImport(WiringPiLibrary, EntryPoint = "wpiPinToGpio", SetLastError = true)]
/// <param name="maker">The maker.</param> public static extern Int32 WpiPinToGpio(Int32 wPiPin);
/// <param name="overVolted">The over volted.</param>
/// <returns>The result code</returns> /// <summary>
[DllImport(WiringPiLibrary, EntryPoint = "piBoardId", SetLastError = true)] /// This returns the BCM_GPIO pin number of the supplied physical pin on the P1 connector.
public static extern int PiBoardId(ref int model, ref int mem, ref int maker, ref int overVolted); /// </summary>
/// <param name="physPin">The physical pin.</param>
/// <summary> /// <returns>The result code</returns>
/// This returns the BCM_GPIO pin number of the supplied wiringPi pin. It takes the board revision into account. [DllImport(WiringPiLibrary, EntryPoint = "physPinToGpio", SetLastError = true)]
/// </summary> public static extern Int32 PhysPinToGpio(Int32 physPin);
/// <param name="wPiPin">The w pi pin.</param>
/// <returns>The result code</returns> /// <summary>
[DllImport(WiringPiLibrary, EntryPoint = "wpiPinToGpio", SetLastError = true)] /// This sets the “strength” of the pad drivers for a particular group of pins.
public static extern int WpiPinToGpio(int wPiPin); /// There are 3 groups of pins and the drive strength is from 0 to 7. Do not use this unless you know what you are doing.
/// </summary>
/// <summary> /// <param name="group">The group.</param>
/// This returns the BCM_GPIO pin number of the supplied physical pin on the P1 connector. /// <param name="value">The value.</param>
/// </summary> /// <returns>The result code</returns>
/// <param name="physPin">The physical pin.</param> [DllImport(WiringPiLibrary, EntryPoint = "setPadDrive", SetLastError = true)]
/// <returns>The result code</returns> public static extern Int32 SetPadDrive(Int32 group, Int32 value);
[DllImport(WiringPiLibrary, EntryPoint = "physPinToGpio", SetLastError = true)]
public static extern int PhysPinToGpio(int physPin); /// <summary>
/// Undocumented function
/// <summary> /// </summary>
/// This sets the “strength” of the pad drivers for a particular group of pins. /// <param name="pin">The pin.</param>
/// There are 3 groups of pins and the drive strength is from 0 to 7. Do not use this unless you know what you are doing. /// <returns>The result code</returns>
/// </summary> [DllImport(WiringPiLibrary, EntryPoint = "getAlt", SetLastError = true)]
/// <param name="group">The group.</param> public static extern Int32 GetAlt(Int32 pin);
/// <param name="value">The value.</param>
/// <returns>The result code</returns> /// <summary>
[DllImport(WiringPiLibrary, EntryPoint = "setPadDrive", SetLastError = true)] /// Undocumented function
public static extern int SetPadDrive(int group, int value); /// </summary>
/// <param name="pin">The pin.</param>
/// <summary> /// <param name="freq">The freq.</param>
/// Undocumented function /// <returns>The result code</returns>
/// </summary> [DllImport(WiringPiLibrary, EntryPoint = "pwmToneWrite", SetLastError = true)]
/// <param name="pin">The pin.</param> public static extern Int32 PwmToneWrite(Int32 pin, Int32 freq);
/// <returns>The result code</returns>
[DllImport(WiringPiLibrary, EntryPoint = "getAlt", SetLastError = true)] /// <summary>
public static extern int GetAlt(int pin); /// This writes the 8-bit byte supplied to the first 8 GPIO pins.
/// Its the fastest way to set all 8 bits at once to a particular value, although it still takes two write operations to the Pis GPIO hardware.
/// <summary> /// </summary>
/// Undocumented function /// <param name="value">The value.</param>
/// </summary> [DllImport(WiringPiLibrary, EntryPoint = "digitalWriteByte", SetLastError = true)]
/// <param name="pin">The pin.</param> public static extern void DigitalWriteByte(Int32 value);
/// <param name="freq">The freq.</param>
/// <returns>The result code</returns> /// <summary>
[DllImport(WiringPiLibrary, EntryPoint = "pwmToneWrite", SetLastError = true)] /// This writes the 8-bit byte supplied to the first 8 GPIO pins.
public static extern int PwmToneWrite(int pin, int freq); /// Its the fastest way to set all 8 bits at once to a particular value, although it still takes two write operations to the Pis GPIO hardware.
/// </summary>
/// <summary> /// <param name="value">The value.</param>
/// This writes the 8-bit byte supplied to the first 8 GPIO pins. [DllImport(WiringPiLibrary, EntryPoint = "digitalWriteByte2", SetLastError = true)]
/// Its the fastest way to set all 8 bits at once to a particular value, although it still takes two write operations to the Pis GPIO hardware. public static extern void DigitalWriteByte2(Int32 value);
/// </summary>
/// <param name="value">The value.</param> /// <summary>
[DllImport(WiringPiLibrary, EntryPoint = "digitalWriteByte", SetLastError = true)] /// Undocumented function
public static extern void DigitalWriteByte(int value); /// This reads the 8-bit byte supplied to the first 8 GPIO pins.
/// Its the fastest way to get all 8 bits at once to a particular value.
/// <summary> /// </summary>
/// This writes the 8-bit byte supplied to the first 8 GPIO pins. /// <returns>The result code</returns>
/// Its the fastest way to set all 8 bits at once to a particular value, although it still takes two write operations to the Pis GPIO hardware. [DllImport(WiringPiLibrary, EntryPoint = "digitalReadByte", SetLastError = true)]
/// </summary> public static extern UInt32 DigitalReadByte();
/// <param name="value">The value.</param>
[DllImport(WiringPiLibrary, EntryPoint = "digitalWriteByte2", SetLastError = true)] /// <summary>
public static extern void DigitalWriteByte2(int value); /// Undocumented function
/// This reads the 8-bit byte supplied to the first 8 GPIO pins.
/// <summary> /// Its the fastest way to get all 8 bits at once to a particular value.
/// Undocumented function /// </summary>
/// This reads the 8-bit byte supplied to the first 8 GPIO pins. /// <returns>The result code</returns>
/// Its the fastest way to get all 8 bits at once to a particular value. [DllImport(WiringPiLibrary, EntryPoint = "digitalReadByte2", SetLastError = true)]
/// </summary> public static extern UInt32 DigitalReadByte2();
/// <returns>The result code</returns>
[DllImport(WiringPiLibrary, EntryPoint = "digitalReadByte", SetLastError = true)] /// <summary>
public static extern uint DigitalReadByte(); /// The PWM generator can run in 2 modes “balanced” and “mark:space”. The mark:space mode is traditional,
/// however the default mode in the Pi is “balanced”. You can switch modes by supplying the parameter: PWM_MODE_BAL or PWM_MODE_MS.
/// <summary> /// </summary>
/// Undocumented function /// <param name="mode">The mode.</param>
/// This reads the 8-bit byte supplied to the first 8 GPIO pins. [DllImport(WiringPiLibrary, EntryPoint = "pwmSetMode", SetLastError = true)]
/// Its the fastest way to get all 8 bits at once to a particular value. public static extern void PwmSetMode(Int32 mode);
/// </summary>
/// <returns>The result code</returns> /// <summary>
[DllImport(WiringPiLibrary, EntryPoint = "digitalReadByte2", SetLastError = true)] /// This sets the range register in the PWM generator. The default is 1024.
public static extern uint DigitalReadByte2(); /// </summary>
/// <param name="range">The range.</param>
/// <summary> [DllImport(WiringPiLibrary, EntryPoint = "pwmSetRange", SetLastError = true)]
/// The PWM generator can run in 2 modes “balanced” and “mark:space”. The mark:space mode is traditional, public static extern void PwmSetRange(UInt32 range);
/// however the default mode in the Pi is “balanced”. You can switch modes by supplying the parameter: PWM_MODE_BAL or PWM_MODE_MS.
/// </summary> /// <summary>
/// <param name="mode">The mode.</param> /// This sets the divisor for the PWM clock.
[DllImport(WiringPiLibrary, EntryPoint = "pwmSetMode", SetLastError = true)] /// Note: The PWM control functions can not be used when in Sys mode.
public static extern void PwmSetMode(int mode); /// To understand more about the PWM system, youll need to read the Broadcom ARM peripherals manual.
/// </summary>
/// <summary> /// <param name="divisor">The divisor.</param>
/// This sets the range register in the PWM generator. The default is 1024. [DllImport(WiringPiLibrary, EntryPoint = "pwmSetClock", SetLastError = true)]
/// </summary> public static extern void PwmSetClock(Int32 divisor);
/// <param name="range">The range.</param>
[DllImport(WiringPiLibrary, EntryPoint = "pwmSetRange", SetLastError = true)] /// <summary>
public static extern void PwmSetRange(uint range); /// Undocumented function
/// </summary>
/// <summary> /// <param name="pin">The pin.</param>
/// This sets the divisor for the PWM clock. /// <param name="freq">The freq.</param>
/// Note: The PWM control functions can not be used when in Sys mode. [DllImport(WiringPiLibrary, EntryPoint = "gpioClockSet", SetLastError = true)]
/// To understand more about the PWM system, youll need to read the Broadcom ARM peripherals manual. public static extern void GpioClockSet(Int32 pin, Int32 freq);
/// </summary>
/// <param name="divisor">The divisor.</param> /// <summary>
[DllImport(WiringPiLibrary, EntryPoint = "pwmSetClock", SetLastError = true)] /// Note: Jan 2013: The waitForInterrupt() function is deprecated you should use the newer and easier to use wiringPiISR() function below.
public static extern void PwmSetClock(int divisor); /// When called, it will wait for an interrupt event to happen on that pin and your program will be stalled. The timeOut parameter is given in milliseconds,
/// or can be -1 which means to wait forever.
/// <summary> /// The return value is -1 if an error occurred (and errno will be set appropriately), 0 if it timed out, or 1 on a successful interrupt event.
/// Undocumented function /// Before you call waitForInterrupt, you must first initialise the GPIO pin and at present the only way to do this is to use the gpio program, either
/// </summary> /// in a script, or using the system() call from inside your program.
/// <param name="pin">The pin.</param> /// e.g. We want to wait for a falling-edge interrupt on GPIO pin 0, so to setup the hardware, we need to run: gpio edge 0 falling
/// <param name="freq">The freq.</param> /// before running the program.
[DllImport(WiringPiLibrary, EntryPoint = "gpioClockSet", SetLastError = true)] /// </summary>
public static extern void GpioClockSet(int pin, int freq); /// <param name="pin">The pin.</param>
/// <param name="timeout">The timeout.</param>
/// <summary> /// <returns>The result code</returns>
/// Note: Jan 2013: The waitForInterrupt() function is deprecated you should use the newer and easier to use wiringPiISR() function below. [Obsolete]
/// When called, it will wait for an interrupt event to happen on that pin and your program will be stalled. The timeOut parameter is given in milliseconds, [DllImport(WiringPiLibrary, EntryPoint = "waitForInterrupt", SetLastError = true)]
/// or can be -1 which means to wait forever. public static extern Int32 WaitForInterrupt(Int32 pin, Int32 timeout);
/// The return value is -1 if an error occurred (and errno will be set appropriately), 0 if it timed out, or 1 on a successful interrupt event.
/// Before you call waitForInterrupt, you must first initialise the GPIO pin and at present the only way to do this is to use the gpio program, either /// <summary>
/// in a script, or using the system() call from inside your program. /// This function registers a function to received interrupts on the specified pin.
/// e.g. We want to wait for a falling-edge interrupt on GPIO pin 0, so to setup the hardware, we need to run: gpio edge 0 falling /// The edgeType parameter is either INT_EDGE_FALLING, INT_EDGE_RISING, INT_EDGE_BOTH or INT_EDGE_SETUP.
/// before running the program. /// If it is INT_EDGE_SETUP then no initialisation of the pin will happen its assumed that you have already setup the pin elsewhere
/// </summary> /// (e.g. with the gpio program), but if you specify one of the other types, then the pin will be exported and initialised as specified.
/// <param name="pin">The pin.</param> /// This is accomplished via a suitable call to the gpio utility program, so it need to be available.
/// <param name="timeout">The timeout.</param> /// The pin number is supplied in the current mode native wiringPi, BCM_GPIO, physical or Sys modes.
/// <returns>The result code</returns> /// This function will work in any mode, and does not need root privileges to work.
[Obsolete] /// The function will be called when the interrupt triggers. When it is triggered, its cleared in the dispatcher before calling your function,
[DllImport(WiringPiLibrary, EntryPoint = "waitForInterrupt", SetLastError = true)] /// so if a subsequent interrupt fires before you finish your handler, then it wont be missed. (However it can only track one more interrupt,
public static extern int WaitForInterrupt(int pin, int timeout); /// if more than one interrupt fires while one is being handled then they will be ignored)
/// This function is run at a high priority (if the program is run using sudo, or as root) and executes concurrently with the main program.
/// <summary> /// It has full access to all the global variables, open file handles and so on.
/// This function registers a function to received interrupts on the specified pin. /// </summary>
/// The edgeType parameter is either INT_EDGE_FALLING, INT_EDGE_RISING, INT_EDGE_BOTH or INT_EDGE_SETUP. /// <param name="pin">The pin.</param>
/// If it is INT_EDGE_SETUP then no initialisation of the pin will happen its assumed that you have already setup the pin elsewhere /// <param name="mode">The mode.</param>
/// (e.g. with the gpio program), but if you specify one of the other types, then the pin will be exported and initialised as specified. /// <param name="method">The method.</param>
/// This is accomplished via a suitable call to the gpio utility program, so it need to be available. /// <returns>The result code</returns>
/// The pin number is supplied in the current mode native wiringPi, BCM_GPIO, physical or Sys modes. [DllImport(WiringPiLibrary, EntryPoint = "wiringPiISR", SetLastError = true)]
/// This function will work in any mode, and does not need root privileges to work. public static extern Int32 WiringPiISR(Int32 pin, Int32 mode, InterruptServiceRoutineCallback method);
/// The function will be called when the interrupt triggers. When it is triggered, its cleared in the dispatcher before calling your function,
/// so if a subsequent interrupt fires before you finish your handler, then it wont be missed. (However it can only track one more interrupt, /// <summary>
/// if more than one interrupt fires while one is being handled then they will be ignored) /// This function creates a thread which is another function in your program previously declared using the PI_THREAD declaration.
/// This function is run at a high priority (if the program is run using sudo, or as root) and executes concurrently with the main program. /// This function is then run concurrently with your main program. An example may be to have this function wait for an interrupt while
/// It has full access to all the global variables, open file handles and so on. /// your program carries on doing other tasks. The thread can indicate an event, or action by using global variables to
/// </summary> /// communicate back to the main program, or other threads.
/// <param name="pin">The pin.</param> /// </summary>
/// <param name="mode">The mode.</param> /// <param name="method">The method.</param>
/// <param name="method">The method.</param> /// <returns>The result code</returns>
/// <returns>The result code</returns> [DllImport(WiringPiLibrary, EntryPoint = "piThreadCreate", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiISR", SetLastError = true)] public static extern Int32 PiThreadCreate(ThreadWorker method);
public static extern int WiringPiISR(int pin, int mode, InterruptServiceRoutineCallback method);
/// <summary>
/// <summary> /// These allow you to synchronise variable updates from your main program to any threads running in your program. keyNum is a number from 0 to 3 and represents a key.
/// This function creates a thread which is another function in your program previously declared using the PI_THREAD declaration. /// When another process tries to lock the same key, it will be stalled until the first process has unlocked the same key.
/// This function is then run concurrently with your main program. An example may be to have this function wait for an interrupt while /// You may need to use these functions to ensure that you get valid data when exchanging data between your main program and a thread
/// your program carries on doing other tasks. The thread can indicate an event, or action by using global variables to /// otherwise its possible that the thread could wake-up halfway during your data copy and change the data
/// communicate back to the main program, or other threads. /// so the data you end up copying is incomplete, or invalid. See the wfi.c program in the examples directory for an example.
/// </summary> /// </summary>
/// <param name="method">The method.</param> /// <param name="key">The key.</param>
/// <returns>The result code</returns> [DllImport(WiringPiLibrary, EntryPoint = "piLock", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "piThreadCreate", SetLastError = true)] public static extern void PiLock(Int32 key);
public static extern int PiThreadCreate(ThreadWorker method);
/// <summary>
/// <summary> /// These allow you to synchronise variable updates from your main program to any threads running in your program. keyNum is a number from 0 to 3 and represents a key.
/// These allow you to synchronise variable updates from your main program to any threads running in your program. keyNum is a number from 0 to 3 and represents a key. /// When another process tries to lock the same key, it will be stalled until the first process has unlocked the same key.
/// When another process tries to lock the same key, it will be stalled until the first process has unlocked the same key. /// You may need to use these functions to ensure that you get valid data when exchanging data between your main program and a thread
/// You may need to use these functions to ensure that you get valid data when exchanging data between your main program and a thread /// otherwise its possible that the thread could wake-up halfway during your data copy and change the data
/// otherwise its possible that the thread could wake-up halfway during your data copy and change the data /// so the data you end up copying is incomplete, or invalid. See the wfi.c program in the examples directory for an example.
/// so the data you end up copying is incomplete, or invalid. See the wfi.c program in the examples directory for an example. /// </summary>
/// </summary> /// <param name="key">The key.</param>
/// <param name="key">The key.</param> [DllImport(WiringPiLibrary, EntryPoint = "piUnlock", SetLastError = true)]
[DllImport(WiringPiLibrary, EntryPoint = "piLock", SetLastError = true)] public static extern void PiUnlock(Int32 key);
public static extern void PiLock(int key);
/// <summary>
/// <summary> /// This attempts to shift your program (or thread in a multi-threaded program) to a higher priority
/// These allow you to synchronise variable updates from your main program to any threads running in your program. keyNum is a number from 0 to 3 and represents a key. /// and enables a real-time scheduling. The priority parameter should be from 0 (the default) to 99 (the maximum).
/// When another process tries to lock the same key, it will be stalled until the first process has unlocked the same key. /// This wont make your program go any faster, but it will give it a bigger slice of time when other programs are running.
/// You may need to use these functions to ensure that you get valid data when exchanging data between your main program and a thread /// The priority parameter works relative to others so you can make one program priority 1 and another priority 2
/// otherwise its possible that the thread could wake-up halfway during your data copy and change the data /// and it will have the same effect as setting one to 10 and the other to 90 (as long as no other
/// so the data you end up copying is incomplete, or invalid. See the wfi.c program in the examples directory for an example. /// programs are running with elevated priorities)
/// </summary> /// The return value is 0 for success and -1 for error. If an error is returned, the program should then consult the errno global variable, as per the usual conventions.
/// <param name="key">The key.</param> /// Note: Only programs running as root can change their priority. If called from a non-root program then nothing happens.
[DllImport(WiringPiLibrary, EntryPoint = "piUnlock", SetLastError = true)] /// </summary>
public static extern void PiUnlock(int key); /// <param name="priority">The priority.</param>
/// <returns>The result code</returns>
/// <summary> [DllImport(WiringPiLibrary, EntryPoint = "piHiPri", SetLastError = true)]
/// This attempts to shift your program (or thread in a multi-threaded program) to a higher priority public static extern Int32 PiHiPri(Int32 priority);
/// and enables a real-time scheduling. The priority parameter should be from 0 (the default) to 99 (the maximum).
/// This wont make your program go any faster, but it will give it a bigger slice of time when other programs are running. /// <summary>
/// The priority parameter works relative to others so you can make one program priority 1 and another priority 2 /// This causes program execution to pause for at least howLong milliseconds.
/// and it will have the same effect as setting one to 10 and the other to 90 (as long as no other /// Due to the multi-tasking nature of Linux it could be longer.
/// programs are running with elevated priorities) /// Note that the maximum delay is an unsigned 32-bit integer or approximately 49 days.
/// The return value is 0 for success and -1 for error. If an error is returned, the program should then consult the errno global variable, as per the usual conventions. /// </summary>
/// Note: Only programs running as root can change their priority. If called from a non-root program then nothing happens. /// <param name="howLong">The how long.</param>
/// </summary> [DllImport(WiringPiLibrary, EntryPoint = "delay", SetLastError = true)]
/// <param name="priority">The priority.</param> public static extern void Delay(UInt32 howLong);
/// <returns>The result code</returns>
[DllImport(WiringPiLibrary, EntryPoint = "piHiPri", SetLastError = true)] /// <summary>
public static extern int PiHiPri(int priority); /// This causes program execution to pause for at least howLong microseconds.
/// Due to the multi-tasking nature of Linux it could be longer.
/// <summary> /// Note that the maximum delay is an unsigned 32-bit integer microseconds or approximately 71 minutes.
/// This causes program execution to pause for at least howLong milliseconds. /// Delays under 100 microseconds are timed using a hard-coded loop continually polling the system time,
/// Due to the multi-tasking nature of Linux it could be longer. /// Delays over 100 microseconds are done using the system nanosleep() function You may need to consider the implications
/// Note that the maximum delay is an unsigned 32-bit integer or approximately 49 days. /// of very short delays on the overall performance of the system, especially if using threads.
/// </summary> /// </summary>
/// <param name="howLong">The how long.</param> /// <param name="howLong">The how long.</param>
[DllImport(WiringPiLibrary, EntryPoint = "delay", SetLastError = true)] [DllImport(WiringPiLibrary, EntryPoint = "delayMicroseconds", SetLastError = true)]
public static extern void Delay(uint howLong); public static extern void DelayMicroseconds(UInt32 howLong);
/// <summary> /// <summary>
/// This causes program execution to pause for at least howLong microseconds. /// This returns a number representing the number of milliseconds since your program called one of the wiringPiSetup functions.
/// Due to the multi-tasking nature of Linux it could be longer. /// It returns an unsigned 32-bit number which wraps after 49 days.
/// Note that the maximum delay is an unsigned 32-bit integer microseconds or approximately 71 minutes. /// </summary>
/// Delays under 100 microseconds are timed using a hard-coded loop continually polling the system time, /// <returns>The result code</returns>
/// Delays over 100 microseconds are done using the system nanosleep() function You may need to consider the implications [DllImport(WiringPiLibrary, EntryPoint = "millis", SetLastError = true)]
/// of very short delays on the overall performance of the system, especially if using threads. public static extern UInt32 Millis();
/// </summary>
/// <param name="howLong">The how long.</param> /// <summary>
[DllImport(WiringPiLibrary, EntryPoint = "delayMicroseconds", SetLastError = true)] /// This returns a number representing the number of microseconds since your program called one of
public static extern void DelayMicroseconds(uint howLong); /// the wiringPiSetup functions. It returns an unsigned 32-bit number which wraps after approximately 71 minutes.
/// </summary>
/// <summary> /// <returns>The result code</returns>
/// This returns a number representing the number of milliseconds since your program called one of the wiringPiSetup functions. [DllImport(WiringPiLibrary, EntryPoint = "micros", SetLastError = true)]
/// It returns an unsigned 32-bit number which wraps after 49 days. public static extern UInt32 Micros();
/// </summary>
/// <returns>The result code</returns> #endregion
[DllImport(WiringPiLibrary, EntryPoint = "millis", SetLastError = true)] }
public static extern uint Millis();
/// <summary>
/// This returns a number representing the number of microseconds since your program called one of
/// the wiringPiSetup functions. It returns an unsigned 32-bit number which wraps after approximately 71 minutes.
/// </summary>
/// <returns>The result code</returns>
[DllImport(WiringPiLibrary, EntryPoint = "micros", SetLastError = true)]
public static extern uint Micros();
#endregion
}
} }

View File

@ -1,110 +1,121 @@
namespace Unosquare.RaspberryIO using Unosquare.RaspberryIO.Camera;
{ using Unosquare.RaspberryIO.Computer;
using Camera; using Unosquare.RaspberryIO.Gpio;
using Computer; using Unosquare.RaspberryIO.Native;
using Gpio; using System.Threading.Tasks;
using Native; using Unosquare.Swan.Components;
using System.Threading.Tasks; using System;
using Swan.Components;
namespace Unosquare.RaspberryIO {
/// <summary> /// <summary>
/// Our main character. Provides access to the Raspberry Pi's GPIO, system and board information and Camera /// Our main character. Provides access to the Raspberry Pi's GPIO, system and board information and Camera
/// </summary> /// </summary>
public static class Pi public static class Pi {
{ private static readonly Object SyncLock = new Object();
private static readonly object SyncLock = new object();
/// <summary>
/// <summary> /// Initializes static members of the <see cref="Pi" /> class.
/// Initializes static members of the <see cref="Pi" /> class. /// </summary>
/// </summary> static Pi() {
static Pi() lock(SyncLock) {
{ // Extraction of embedded resources
lock (SyncLock) Resources.EmbeddedResources.ExtractAll();
{
// Extraction of embedded resources // Instance assignments
Resources.EmbeddedResources.ExtractAll(); Gpio = GpioController.Instance;
Info = SystemInfo.Instance;
// Instance assignments Timing = Timing.Instance;
Gpio = GpioController.Instance; Spi = SpiBus.Instance;
Info = SystemInfo.Instance; I2C = I2CBus.Instance;
Timing = Timing.Instance; Camera = CameraController.Instance;
Spi = SpiBus.Instance; PiDisplay = DsiDisplay.Instance;
I2C = I2CBus.Instance; }
Camera = CameraController.Instance; }
PiDisplay = DsiDisplay.Instance;
} #region Components
}
/// <summary>
#region Components /// Provides access to the Raspberry Pi's GPIO as a collection of GPIO Pins.
/// </summary>
/// <summary> public static GpioController Gpio {
/// Provides access to the Raspberry Pi's GPIO as a collection of GPIO Pins. get;
/// </summary> }
public static GpioController Gpio { get; }
/// <summary>
/// <summary> /// Provides information on this Raspberry Pi's CPU and form factor.
/// Provides information on this Raspberry Pi's CPU and form factor. /// </summary>
/// </summary> public static SystemInfo Info {
public static SystemInfo Info { get; } get;
}
/// <summary>
/// Provides access to The PI's Timing and threading API /// <summary>
/// </summary> /// Provides access to The PI's Timing and threading API
public static Timing Timing { get; } /// </summary>
public static Timing Timing {
/// <summary> get;
/// Provides access to the 2-channel SPI bus }
/// </summary>
public static SpiBus Spi { get; } /// <summary>
/// Provides access to the 2-channel SPI bus
/// <summary> /// </summary>
/// Provides access to the functionality of the i2c bus. public static SpiBus Spi {
/// </summary> get;
public static I2CBus I2C { get; } }
/// <summary> /// <summary>
/// Provides access to the official Raspberry Pi Camera /// Provides access to the functionality of the i2c bus.
/// </summary> /// </summary>
public static CameraController Camera { get; } public static I2CBus I2C {
get;
/// <summary> }
/// Provides access to the official Raspberry Pi 7-inch DSI Display
/// </summary> /// <summary>
public static DsiDisplay PiDisplay { get; } /// Provides access to the official Raspberry Pi Camera
/// </summary>
/// <summary> public static CameraController Camera {
/// Gets the logger source name. get;
/// </summary> }
internal static string LoggerSource => typeof(Pi).Namespace;
/// <summary>
#endregion /// Provides access to the official Raspberry Pi 7-inch DSI Display
/// </summary>
#region Methods public static DsiDisplay PiDisplay {
get;
/// <summary> }
/// Restarts the Pi. Must be running as SU
/// </summary> /// <summary>
/// <returns>The process result</returns> /// Gets the logger source name.
public static async Task<ProcessResult> RestartAsync() => await ProcessRunner.GetProcessResultAsync("reboot", null, null); /// </summary>
internal static String LoggerSource => typeof(Pi).Namespace;
/// <summary>
/// Restarts the Pi. Must be running as SU #endregion
/// </summary>
/// <returns>The process result</returns> #region Methods
public static ProcessResult Restart() => RestartAsync().GetAwaiter().GetResult();
/// <summary>
/// <summary> /// Restarts the Pi. Must be running as SU
/// Halts the Pi. Must be running as SU /// </summary>
/// </summary> /// <returns>The process result</returns>
/// <returns>The process result</returns> public static async Task<ProcessResult> RestartAsync() => await ProcessRunner.GetProcessResultAsync("reboot", null, null);
public static async Task<ProcessResult> ShutdownAsync() => await ProcessRunner.GetProcessResultAsync("halt", null, null);
/// <summary>
/// <summary> /// Restarts the Pi. Must be running as SU
/// Halts the Pi. Must be running as SU /// </summary>
/// </summary> /// <returns>The process result</returns>
/// <returns>The process result</returns> public static ProcessResult Restart() => RestartAsync().GetAwaiter().GetResult();
public static ProcessResult Shutdown() => ShutdownAsync().GetAwaiter().GetResult();
/// <summary>
#endregion /// Halts the Pi. Must be running as SU
} /// </summary>
} /// <returns>The process result</returns>
public static async Task<ProcessResult> ShutdownAsync() => await ProcessRunner.GetProcessResultAsync("halt", null, null);
/// <summary>
/// Halts the Pi. Must be running as SU
/// </summary>
/// <returns>The process result</returns>
public static ProcessResult Shutdown() => ShutdownAsync().GetAwaiter().GetResult();
#endregion
}
}

View File

@ -1,65 +1,55 @@
namespace Unosquare.RaspberryIO.Resources using Unosquare.RaspberryIO.Native;
{ using Unosquare.Swan;
using Native; using System;
using Swan; using System.Collections.ObjectModel;
using System; using System.IO;
using System.Collections.ObjectModel;
using System.IO; namespace Unosquare.RaspberryIO.Resources {
/// <summary>
/// Provides access to embedded assembly files
/// </summary>
internal static class EmbeddedResources {
/// <summary> /// <summary>
/// Provides access to embedded assembly files /// Initializes static members of the <see cref="EmbeddedResources"/> class.
/// </summary> /// </summary>
internal static class EmbeddedResources static EmbeddedResources() => ResourceNames = new ReadOnlyCollection<String>(typeof(EmbeddedResources).Assembly().GetManifestResourceNames());
{
/// <summary> /// <summary>
/// Initializes static members of the <see cref="EmbeddedResources"/> class. /// Gets the resource names.
/// </summary> /// </summary>
static EmbeddedResources() /// <value>
{ /// The resource names.
ResourceNames = /// </value>
new ReadOnlyCollection<string>(typeof(EmbeddedResources).Assembly().GetManifestResourceNames()); public static ReadOnlyCollection<String> ResourceNames {
} get;
}
/// <summary>
/// Gets the resource names. /// <summary>
/// </summary> /// Extracts all the file resources to the specified base path.
/// <value> /// </summary>
/// The resource names. public static void ExtractAll() {
/// </value> String basePath = Runtime.EntryAssemblyDirectory;
public static ReadOnlyCollection<string> ResourceNames { get; } Int32 executablePermissions = Standard.StringToInteger("0777", IntPtr.Zero, 8);
/// <summary> foreach(String resourceName in ResourceNames) {
/// Extracts all the file resources to the specified base path. String filename = resourceName.Substring($"{typeof(EmbeddedResources).Namespace}.".Length);
/// </summary> String targetPath = Path.Combine(basePath, filename);
public static void ExtractAll() if(File.Exists(targetPath)) {
{ return;
var basePath = Runtime.EntryAssemblyDirectory; }
var executablePermissions = Standard.StringToInteger("0777", IntPtr.Zero, 8);
using(Stream stream = typeof(EmbeddedResources).Assembly().GetManifestResourceStream($"{typeof(EmbeddedResources).Namespace}.{filename}")) {
foreach (var resourceName in ResourceNames) using(FileStream outputStream = File.OpenWrite(targetPath)) {
{ stream?.CopyTo(outputStream);
var filename = resourceName.Substring($"{typeof(EmbeddedResources).Namespace}.".Length); }
var targetPath = Path.Combine(basePath, filename);
if (File.Exists(targetPath)) return; try {
_ = Standard.Chmod(targetPath, (UInt32)executablePermissions);
using (var stream = typeof(EmbeddedResources).Assembly() } catch {
.GetManifestResourceStream($"{typeof(EmbeddedResources).Namespace}.{filename}")) /* Ignore */
{ }
using (var outputStream = File.OpenWrite(targetPath)) }
{ }
stream?.CopyTo(outputStream); }
} }
try
{
Standard.Chmod(targetPath, (uint)executablePermissions);
}
catch
{
/* Ignore */
}
}
}
}
}
} }

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>
{ /// <summary>
private long _backingValue; /// Gets or sets the value.
/// </summary>
/// <summary> public T Value {
/// Initializes a new instance of the <see cref="AtomicTypeBase{T}"/> class. get => this.FromLong(this.BackingValue);
/// </summary> set => this.BackingValue = this.ToLong(value);
/// <param name="initialValue">The initial value.</param> }
protected AtomicTypeBase(long initialValue)
{ /// <summary>
BackingValue = initialValue; /// Gets or sets the backing value.
} /// </summary>
protected Int64 BackingValue {
/// <summary> get => Interlocked.Read(ref this._backingValue);
/// Gets or sets the value. set => Interlocked.Exchange(ref this._backingValue, value);
/// </summary> }
public T Value
{ /// <summary>
get => FromLong(BackingValue); /// Implements the operator ==.
set => BackingValue = ToLong(value); /// </summary>
} /// <param name="a">a.</param>
/// <param name="b">The b.</param>
/// <summary> /// <returns>
/// Gets or sets the backing value. /// The result of the operator.
/// </summary> /// </returns>
protected long BackingValue public static Boolean operator ==(AtomicTypeBase<T> a, T b) => a?.Equals(b) == true;
{
get => Interlocked.Read(ref _backingValue); /// <summary>
set => Interlocked.Exchange(ref _backingValue, value); /// Implements the operator !=.
} /// </summary>
/// <param name="a">a.</param>
/// <summary> /// <param name="b">The b.</param>
/// Implements the operator ==. /// <returns>
/// </summary> /// The result of the operator.
/// <param name="a">a.</param> /// </returns>
/// <param name="b">The b.</param> public static Boolean operator !=(AtomicTypeBase<T> a, T b) => a?.Equals(b) == false;
/// <returns>
/// The result of the operator. /// <summary>
/// </returns> /// Implements the operator &gt;.
public static bool operator ==(AtomicTypeBase<T> a, T b) => a?.Equals(b) == true; /// </summary>
/// <param name="a">a.</param>
/// <summary> /// <param name="b">The b.</param>
/// Implements the operator !=. /// <returns>
/// </summary> /// The result of the operator.
/// <param name="a">a.</param> /// </returns>
/// <param name="b">The b.</param> public static Boolean operator >(AtomicTypeBase<T> a, T b) => a.CompareTo(b) > 0;
/// <returns>
/// The result of the operator. /// <summary>
/// </returns> /// Implements the operator &lt;.
public static bool operator !=(AtomicTypeBase<T> a, T b) => a?.Equals(b) == false; /// </summary>
/// <param name="a">a.</param>
/// <summary> /// <param name="b">The b.</param>
/// Implements the operator &gt;. /// <returns>
/// </summary> /// The result of the operator.
/// <param name="a">a.</param> /// </returns>
/// <param name="b">The b.</param> public static Boolean operator <(AtomicTypeBase<T> a, T b) => a.CompareTo(b) < 0;
/// <returns>
/// The result of the operator. /// <summary>
/// </returns> /// Implements the operator &gt;=.
public static bool operator >(AtomicTypeBase<T> a, T b) => a.CompareTo(b) > 0; /// </summary>
/// <param name="a">a.</param>
/// <summary> /// <param name="b">The b.</param>
/// Implements the operator &lt;. /// <returns>
/// </summary> /// The result of the operator.
/// <param name="a">a.</param> /// </returns>
/// <param name="b">The b.</param> public static Boolean operator >=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) >= 0;
/// <returns>
/// The result of the operator. /// <summary>
/// </returns> /// Implements the operator &lt;=.
public static bool operator <(AtomicTypeBase<T> a, T b) => a.CompareTo(b) < 0; /// </summary>
/// <param name="a">a.</param>
/// <summary> /// <param name="b">The b.</param>
/// Implements the operator &gt;=. /// <returns>
/// </summary> /// The result of the operator.
/// <param name="a">a.</param> /// </returns>
/// <param name="b">The b.</param> public static Boolean operator <=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) <= 0;
/// <returns>
/// The result of the operator. /// <summary>
/// </returns> /// Implements the operator ++.
public static bool operator >=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) >= 0; /// </summary>
/// <param name="instance">The instance.</param>
/// <summary> /// <returns>
/// Implements the operator &lt;=. /// The result of the operator.
/// </summary> /// </returns>
/// <param name="a">a.</param> public static AtomicTypeBase<T> operator ++(AtomicTypeBase<T> instance) {
/// <param name="b">The b.</param> _ = Interlocked.Increment(ref instance._backingValue);
/// <returns> return instance;
/// The result of the operator. }
/// </returns>
public static bool operator <=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) <= 0; /// <summary>
/// Implements the operator --.
/// <summary> /// </summary>
/// Implements the operator ++. /// <param name="instance">The instance.</param>
/// </summary> /// <returns>
/// <param name="instance">The instance.</param> /// The result of the operator.
/// <returns> /// </returns>
/// The result of the operator. public static AtomicTypeBase<T> operator --(AtomicTypeBase<T> instance) {
/// </returns> _ = Interlocked.Decrement(ref instance._backingValue);
public static AtomicTypeBase<T> operator ++(AtomicTypeBase<T> instance) return instance;
{ }
Interlocked.Increment(ref instance._backingValue);
return instance; /// <summary>
} /// Implements the operator -&lt;.
/// </summary>
/// <summary> /// <param name="instance">The instance.</param>
/// Implements the operator --. /// <param name="operand">The operand.</param>
/// </summary> /// <returns>
/// <param name="instance">The instance.</param> /// The result of the operator.
/// <returns> /// </returns>
/// The result of the operator. public static AtomicTypeBase<T> operator +(AtomicTypeBase<T> instance, Int64 operand) {
/// </returns> instance.BackingValue += operand;
public static AtomicTypeBase<T> operator --(AtomicTypeBase<T> instance) return instance;
{ }
Interlocked.Decrement(ref instance._backingValue);
return instance; /// <summary>
} /// Implements the operator -.
/// </summary>
/// <summary> /// <param name="instance">The instance.</param>
/// Implements the operator -&lt;. /// <param name="operand">The operand.</param>
/// </summary> /// <returns>
/// <param name="instance">The instance.</param> /// The result of the operator.
/// <param name="operand">The operand.</param> /// </returns>
/// <returns> public static AtomicTypeBase<T> operator -(AtomicTypeBase<T> instance, Int64 operand) {
/// The result of the operator. instance.BackingValue -= operand;
/// </returns> return instance;
public static AtomicTypeBase<T> operator +(AtomicTypeBase<T> instance, long operand) }
{
instance.BackingValue = instance.BackingValue + operand; /// <summary>
return instance; /// Compares the value to the other instance.
} /// </summary>
/// <param name="other">The other instance.</param>
/// <summary> /// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
/// Implements the operator -. /// <exception cref="ArgumentException">When types are incompatible.</exception>
/// </summary> public Int32 CompareTo(Object other) {
/// <param name="instance">The instance.</param> switch(other) {
/// <param name="operand">The operand.</param> case null:
/// <returns> return 1;
/// The result of the operator. case AtomicTypeBase<T> atomic:
/// </returns> return this.BackingValue.CompareTo(atomic.BackingValue);
public static AtomicTypeBase<T> operator -(AtomicTypeBase<T> instance, long operand) case T variable:
{ return this.Value.CompareTo(variable);
instance.BackingValue = instance.BackingValue - operand; }
return instance;
} throw new ArgumentException("Incompatible comparison types");
}
/// <summary>
/// Compares the value to the other instance. /// <summary>
/// </summary> /// Compares the value to the other instance.
/// <param name="other">The other instance.</param> /// </summary>
/// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns> /// <param name="other">The other instance.</param>
/// <exception cref="ArgumentException">When types are incompatible.</exception> /// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
public int CompareTo(object other) public Int32 CompareTo(T other) => this.Value.CompareTo(other);
{
switch (other) /// <summary>
{ /// Compares the value to the other instance.
case null: /// </summary>
return 1; /// <param name="other">The other instance.</param>
case AtomicTypeBase<T> atomic: /// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
return BackingValue.CompareTo(atomic.BackingValue); public Int32 CompareTo(AtomicTypeBase<T> other) => this.BackingValue.CompareTo(other?.BackingValue ?? default);
case T variable:
return Value.CompareTo(variable); /// <summary>
} /// Determines whether the specified <see cref="Object" />, is equal to this instance.
/// </summary>
throw new ArgumentException("Incompatible comparison types"); /// <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>.
/// <summary> /// </returns>
/// Compares the value to the other instance. public override Boolean Equals(Object other) {
/// </summary> switch(other) {
/// <param name="other">The other instance.</param> case AtomicTypeBase<T> atomic:
/// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns> return this.Equals(atomic);
public int CompareTo(T other) => Value.CompareTo(other); case T variable:
return this.Equals(variable);
/// <summary> }
/// Compares the value to the other instance.
/// </summary> return false;
/// <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>
/// Returns a hash code for this instance.
/// <summary> /// </summary>
/// Determines whether the specified <see cref="object" />, is equal to this instance. /// <returns>
/// </summary> /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// <param name="other">The <see cref="object" /> to compare with this instance.</param> /// </returns>
/// <returns> public override Int32 GetHashCode() => this.BackingValue.GetHashCode();
/// <c>true</c> if the specified <see cref="object" /> is equal to this instance; otherwise, <c>false</c>.
/// </returns> /// <inheritdoc />
public override bool Equals(object other) public Boolean Equals(AtomicTypeBase<T> other) =>
{ this.BackingValue == (other?.BackingValue ?? default);
switch (other)
{ /// <inheritdoc />
case AtomicTypeBase<T> atomic: public Boolean Equals(T other) => Equals(this.Value, other);
return Equals(atomic);
case T variable: /// <summary>
return Equals(variable); /// Converts from a long value to the target type.
} /// </summary>
/// <param name="backingValue">The backing value.</param>
return false; /// <returns>The value converted form a long value.</returns>
} protected abstract T FromLong(Int64 backingValue);
/// <summary> /// <summary>
/// Returns a hash code for this instance. /// Converts from the target type to a long value.
/// </summary> /// </summary>
/// <returns> /// <param name="value">The value.</param>
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// <returns>The value converted to a long value.</returns>
/// </returns> protected abstract Int64 ToLong(T value);
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);
}
} }

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

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> /// <summary>
/// Represents a generic expression parser. /// Resolves the expression.
/// </summary> /// </summary>
public abstract class ExpressionParser /// <typeparam name="T">The type of expression result.</typeparam>
{ /// <param name="tokens">The tokens.</param>
/// <summary> /// <returns>The representation of the expression parsed.</returns>
/// Resolves the expression. public virtual T ResolveExpression<T>(IEnumerable<Token> tokens) {
/// </summary> UnaryExpression conversion = Expression.Convert(this.Parse(tokens), typeof(T));
/// <typeparam name="T">The type of expression result.</typeparam> return Expression.Lambda<Func<T>>(conversion).Compile()();
/// <param name="tokens">The tokens.</param> }
/// <returns>The representation of the expression parsed.</returns>
public virtual T ResolveExpression<T>(IEnumerable<Token> tokens) /// <summary>
{ /// Parses the specified tokens.
var conversion = Expression.Convert(Parse(tokens), typeof(T)); /// </summary>
return Expression.Lambda<Func<T>>(conversion).Compile()(); /// <param name="tokens">The tokens.</param>
} /// <returns>The final expression.</returns>
public virtual Expression Parse(IEnumerable<Token> tokens) {
/// <summary> List<Stack<Expression>> expressionStack = new List<Stack<Expression>>();
/// Parses the specified tokens.
/// </summary> foreach(Token token in tokens) {
/// <param name="tokens">The tokens.</param> if(expressionStack.Any() == false) {
/// <returns>The final expression.</returns> expressionStack.Add(new Stack<Expression>());
public virtual Expression Parse(IEnumerable<Token> tokens) }
{
var expressionStack = new List<Stack<Expression>>(); switch(token.Type) {
case TokenType.Wall:
foreach (var token in tokens) expressionStack.Add(new Stack<Expression>());
{ break;
if (expressionStack.Any() == false) case TokenType.Number:
expressionStack.Add(new Stack<Expression>()); expressionStack.Last().Push(Expression.Constant(Convert.ToDecimal(token.Value)));
break;
switch (token.Type) case TokenType.Variable:
{ this.ResolveVariable(token.Value, expressionStack.Last());
case TokenType.Wall: break;
expressionStack.Add(new Stack<Expression>()); case TokenType.String:
break; expressionStack.Last().Push(Expression.Constant(token.Value));
case TokenType.Number: break;
expressionStack.Last().Push(Expression.Constant(Convert.ToDecimal(token.Value))); case TokenType.Operator:
break; this.ResolveOperator(token.Value, expressionStack.Last());
case TokenType.Variable: break;
ResolveVariable(token.Value, expressionStack.Last()); case TokenType.Function:
break; this.ResolveFunction(token.Value, expressionStack.Last());
case TokenType.String:
expressionStack.Last().Push(Expression.Constant(token.Value)); if(expressionStack.Count > 1 && expressionStack.Last().Count == 1) {
break; Expression lastValue = expressionStack.Last().Pop();
case TokenType.Operator: _ = expressionStack.Remove(expressionStack.Last());
ResolveOperator(token.Value, expressionStack.Last()); expressionStack.Last().Push(lastValue);
break; }
case TokenType.Function:
ResolveFunction(token.Value, expressionStack.Last()); break;
}
if (expressionStack.Count > 1 && expressionStack.Last().Count == 1) }
{
var lastValue = expressionStack.Last().Pop(); return expressionStack.Last().Pop();
expressionStack.Remove(expressionStack.Last()); }
expressionStack.Last().Push(lastValue);
} /// <summary>
/// Resolves the variable.
break; /// </summary>
} /// <param name="value">The value.</param>
} /// <param name="expressionStack">The expression stack.</param>
public abstract void ResolveVariable(String value, Stack<Expression> expressionStack);
return expressionStack.Last().Pop();
} /// <summary>
/// Resolves the operator.
/// <summary> /// </summary>
/// Resolves the variable. /// <param name="value">The value.</param>
/// </summary> /// <param name="expressionStack">The expression stack.</param>
/// <param name="value">The value.</param> public abstract void ResolveOperator(String value, Stack<Expression> expressionStack);
/// <param name="expressionStack">The expression stack.</param>
public abstract void ResolveVariable(string value, Stack<Expression> expressionStack); /// <summary>
/// Resolves the function.
/// <summary> /// </summary>
/// Resolves the operator. /// <param name="value">The value.</param>
/// </summary> /// <param name="expressionStack">The expression stack.</param>
/// <param name="value">The value.</param> public abstract void ResolveFunction(String value, Stack<Expression> expressionStack);
/// <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> /// <summary>
/// Interface object map. /// Gets or sets the map.
/// </summary> /// </summary>
public interface IObjectMap Dictionary<PropertyInfo, List<PropertyInfo>> Map {
{ get;
/// <summary> }
/// Gets or sets the map.
/// </summary> /// <summary>
Dictionary<PropertyInfo, List<PropertyInfo>> Map { get; } /// Gets or sets the type of the source.
/// </summary>
/// <summary> Type SourceType {
/// Gets or sets the type of the source. get;
/// </summary> }
Type SourceType { get; }
/// <summary>
/// <summary> /// Gets or sets the type of the destination.
/// Gets or sets the type of the destination. /// </summary>
/// </summary> Type DestinationType {
Type DestinationType { get; } 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> /// <summary>
/// Defines a generic interface for synchronized locking mechanisms. /// Acquires a writer 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 AcquireWriterLock();
/// <summary>
/// Acquires a writer lock. /// <summary>
/// The lock is released when the returned locking object is disposed. /// Acquires a reader lock.
/// </summary> /// The lock is released when the returned locking object is disposed.
/// <returns>A disposable locking object.</returns> /// </summary>
IDisposable AcquireWriterLock(); /// <returns>A disposable locking object.</returns>
IDisposable AcquireReaderLock();
/// <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;
{
namespace Unosquare.Swan.Abstractions {
/// <summary>
/// A simple Validator interface.
/// </summary>
public interface IValidator {
/// <summary> /// <summary>
/// A simple Validator interface. /// The error message.
/// </summary> /// </summary>
public interface IValidator String ErrorMessage {
{ get;
/// <summary> }
/// The error message.
/// </summary> /// <summary>
string ErrorMessage { get; } /// Checks if a value is valid.
/// </summary>
/// <summary> /// <typeparam name="T">The type.</typeparam>
/// Checks if a value is valid. /// <param name="value"> The value.</param>
/// </summary> /// <returns>True if it is valid.False if it is not.</returns>
/// <typeparam name="T">The type.</typeparam> Boolean IsValid<T>(T value);
/// <param name="value"> The value.</param> }
/// <returns>True if it is valid.False if it is not.</returns>
bool 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> /// <summary>
/// Provides a generalized API for ManualResetEvent and ManualResetEventSlim. /// Gets a value indicating whether the event is in the completed state.
/// </summary> /// </summary>
/// <seealso cref="IDisposable" /> Boolean IsCompleted {
public interface IWaitEvent : IDisposable get;
{ }
/// <summary>
/// Gets a value indicating whether the event is in the completed state. /// <summary>
/// </summary> /// Gets a value indicating whether the Begin method has been called.
bool IsCompleted { get; } /// It returns false after the Complete method is called.
/// </summary>
/// <summary> Boolean IsInProgress {
/// Gets a value indicating whether the Begin method has been called. get;
/// 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> /// </summary>
/// Returns true if the underlying handle is not closed and it is still valid. Boolean IsValid {
/// </summary> get;
bool IsValid { get; } }
/// <summary> /// <summary>
/// Gets a value indicating whether this instance is disposed. /// Gets a value indicating whether this instance is disposed.
/// </summary> /// </summary>
bool IsDisposed { get; } Boolean IsDisposed {
get;
/// <summary> }
/// Enters the state in which waiters need to wait.
/// All future waiters will block when they call the Wait method. /// <summary>
/// </summary> /// Enters the state in which waiters need to wait.
void Begin(); /// All future waiters will block when they call the Wait method.
/// </summary>
/// <summary> void Begin();
/// Leaves the state in which waiters need to wait.
/// All current waiters will continue. /// <summary>
/// </summary> /// Leaves the state in which waiters need to wait.
void Complete(); /// All current waiters will continue.
/// </summary>
/// <summary> void Complete();
/// Waits for the event to be completed.
/// </summary> /// <summary>
void Wait(); /// Waits for the event to be completed.
/// </summary>
/// <summary> void Wait();
/// Waits for the event to be completed.
/// Returns <c>true</c> when there was no timeout. False if the timeout was reached. /// <summary>
/// </summary> /// Waits for the event to be completed.
/// <param name="timeout">The maximum amount of time to wait for.</param> /// Returns <c>true</c> when there was no timeout. False if the timeout was reached.
/// <returns><c>true</c> when there was no timeout. <c>false</c> if the timeout was reached.</returns> /// </summary>
bool Wait(TimeSpan timeout); /// <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> /// <summary>
/// Should start the task immediately and asynchronously. /// Should stop the task immediately and synchronously.
/// </summary> /// </summary>
void Start(); void Stop();
}
/// <summary>
/// Should stop the task immediately and synchronously.
/// </summary>
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> /// <summary>
/// Initializes a new instance of the <see cref="RunnerBase"/> class. /// Gets the error messages.
/// </summary> /// </summary>
/// <param name="isEnabled">if set to <c>true</c> [is enabled].</param> /// <value>
protected RunnerBase(bool isEnabled) /// The error messages.
{ /// </value>
Name = GetType().Name; public List<String> ErrorMessages { get; } = new List<String>();
IsEnabled = isEnabled;
} /// <summary>
/// Gets the name.
/// <summary> /// </summary>
/// Gets the error messages. /// <value>
/// </summary> /// The name.
/// <value> /// </value>
/// The error messages. public String Name {
/// </value> get;
public List<string> ErrorMessages { get; } = new List<string>(); }
/// <summary> /// <summary>
/// Gets the name. /// Gets a value indicating whether this instance is running.
/// </summary> /// </summary>
/// <value> /// <value>
/// The name. /// <c>true</c> if this instance is running; otherwise, <c>false</c>.
/// </value> /// </value>
public string Name { get; } public Boolean IsRunning {
get; private set;
/// <summary> }
/// Gets a value indicating whether this instance is running.
/// </summary> /// <summary>
/// <value> /// Gets a value indicating whether this instance is enabled.
/// <c>true</c> if this instance is running; otherwise, <c>false</c>. /// </summary>
/// </value> /// <value>
public bool IsRunning { get; private set; } /// <c>true</c> if this instance is enabled; otherwise, <c>false</c>.
/// </value>
/// <summary> public Boolean IsEnabled {
/// Gets a value indicating whether this instance is enabled. get;
/// </summary> }
/// <value>
/// <c>true</c> if this instance is enabled; otherwise, <c>false</c>. /// <summary>
/// </value> /// Starts this instance.
public bool IsEnabled { get; } /// </summary>
public virtual void Start() {
/// <summary> if(this.IsEnabled == false) {
/// Starts this instance. return;
/// </summary> }
public virtual void Start()
{ $"Start Requested".Debug(this.Name);
if (IsEnabled == false) this._cancelTokenSource = new CancellationTokenSource();
return; this._workFinished = new ManualResetEvent(false);
$"Start Requested".Debug(Name); this._worker = new Thread(() => {
_cancelTokenSource = new CancellationTokenSource(); _ = this._workFinished.Reset();
_workFinished = new ManualResetEvent(false); this.IsRunning = true;
try {
_worker = new Thread(() => this.Setup();
{ this.DoBackgroundWork(this._cancelTokenSource.Token);
_workFinished.Reset(); } catch(ThreadAbortException) {
IsRunning = true; $"{nameof(ThreadAbortException)} caught.".Warn(this.Name);
try } catch(Exception ex) {
{ $"{ex.GetType()}: {ex.Message}\r\n{ex.StackTrace}".Error(this.Name);
Setup(); } finally {
DoBackgroundWork(_cancelTokenSource.Token); this.Cleanup();
} _ = this._workFinished?.Set();
catch (ThreadAbortException) this.IsRunning = false;
{ "Stopped Completely".Debug(this.Name);
$"{nameof(ThreadAbortException)} caught.".Warn(Name); }
} }) {
catch (Exception ex) IsBackground = true,
{ Name = $"{this.Name}Thread",
$"{ex.GetType()}: {ex.Message}\r\n{ex.StackTrace}".Error(Name); };
}
finally this._worker.Start();
{ }
Cleanup();
_workFinished?.Set(); /// <summary>
IsRunning = false; /// Stops this instance.
"Stopped Completely".Debug(Name); /// </summary>
} public virtual void Stop() {
}) if(this.IsEnabled == false || this.IsRunning == false) {
{ return;
IsBackground = true, }
Name = $"{Name}Thread",
}; $"Stop Requested".Debug(this.Name);
this._cancelTokenSource.Cancel();
_worker.Start(); Int32 waitRetries = 5;
} while(waitRetries >= 1) {
if(this._workFinished.WaitOne(250)) {
/// <summary> waitRetries = -1;
/// Stops this instance. break;
/// </summary> }
public virtual void Stop()
{ waitRetries--;
if (IsEnabled == false || IsRunning == false) }
return;
if(waitRetries < 0) {
$"Stop Requested".Debug(Name); "Workbench stopped gracefully".Debug(this.Name);
_cancelTokenSource.Cancel(); } else {
var waitRetries = 5; "Did not respond to stop request. Aborting thread and waiting . . .".Warn(this.Name);
while (waitRetries >= 1) this._worker.Abort();
{
if (_workFinished.WaitOne(250)) if(this._workFinished.WaitOne(5000) == false) {
{ "Waited and no response. Worker might have been left in an inconsistent state.".Error(this.Name);
waitRetries = -1; } else {
break; "Waited for worker and it finally responded (OK).".Debug(this.Name);
} }
}
waitRetries--;
} this._workFinished.Dispose();
this._workFinished = null;
if (waitRetries < 0) }
{
"Workbench stopped gracefully".Debug(Name); /// <summary>
} /// Setups this instance.
else /// </summary>
{ protected virtual void Setup() {
"Did not respond to stop request. Aborting thread and waiting . . .".Warn(Name); // empty
_worker.Abort(); }
if (_workFinished.WaitOne(5000) == false) /// <summary>
"Waited and no response. Worker might have been left in an inconsistent state.".Error(Name); /// Cleanups this instance.
else /// </summary>
"Waited for worker and it finally responded (OK).".Debug(Name); protected virtual void Cleanup() {
} // empty
}
_workFinished.Dispose();
_workFinished = null; /// <summary>
} /// Does the background work.
/// </summary>
/// <summary> /// <param name="ct">The ct.</param>
/// Setups this instance. protected abstract void DoBackgroundWork(CancellationToken ct);
/// </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 this._global;
}
return _global; }
} }
}
} /// <summary>
/// Reloads the global settings.
/// <summary> /// </summary>
/// Reloads the global settings. public void ReloadGlobalSettings() {
/// </summary> if(File.Exists(this.ConfigurationFilePath) == false || File.ReadAllText(this.ConfigurationFilePath).Length == 0) {
public void ReloadGlobalSettings() this.ResetGlobalSettings();
{ return;
if (File.Exists(ConfigurationFilePath) == false || File.ReadAllText(ConfigurationFilePath).Length == 0) }
{
ResetGlobalSettings(); lock(this._syncRoot) {
return; this._global = Json.Deserialize<T>(File.ReadAllText(this.ConfigurationFilePath));
} }
}
lock (_syncRoot)
_global = Json.Deserialize<T>(File.ReadAllText(ConfigurationFilePath)); /// <summary>
} /// Persists the global settings.
/// </summary>
/// <summary> public void PersistGlobalSettings() => File.WriteAllText(this.ConfigurationFilePath, Json.Serialize(this.Global, true));
/// Persists the global settings.
/// </summary> /// <summary>
public void PersistGlobalSettings() => File.WriteAllText(ConfigurationFilePath, Json.Serialize(Global, true)); /// Updates settings from list.
/// </summary>
/// <summary> /// <param name="propertyList">The list.</param>
/// Updates settings from list. /// <returns>
/// </summary> /// A list of settings of type ref="ExtendedPropertyInfo".
/// <param name="propertyList">The list.</param> /// </returns>
/// <returns> /// <exception cref="ArgumentNullException">propertyList.</exception>
/// A list of settings of type ref="ExtendedPropertyInfo". public List<String> RefreshFromList(List<ExtendedPropertyInfo<T>> propertyList) {
/// </returns> if(propertyList == null) {
/// <exception cref="ArgumentNullException">propertyList.</exception> throw new ArgumentNullException(nameof(propertyList));
public List<string> RefreshFromList(List<ExtendedPropertyInfo<T>> propertyList) }
{
if (propertyList == null) List<String> changedSettings = new List<String>();
throw new ArgumentNullException(nameof(propertyList)); IEnumerable<PropertyInfo> globalProps = Runtime.PropertyTypeCache.RetrieveAllProperties<T>();
var changedSettings = new List<string>(); foreach(ExtendedPropertyInfo<T> property in propertyList) {
var globalProps = Runtime.PropertyTypeCache.RetrieveAllProperties<T>(); PropertyInfo propertyInfo = globalProps.FirstOrDefault(x => x.Name == property.Property);
foreach (var property in propertyList) if(propertyInfo == null) {
{ continue;
var propertyInfo = globalProps.FirstOrDefault(x => x.Name == property.Property); }
if (propertyInfo == null) continue; Object originalValue = propertyInfo.GetValue(this.Global);
Boolean isChanged = propertyInfo.PropertyType.IsArray
var originalValue = propertyInfo.GetValue(Global); ? property.Value is IEnumerable enumerable && propertyInfo.TrySetArray(enumerable.Cast<Object>(), this.Global)
var isChanged = propertyInfo.PropertyType.IsArray : this.SetValue(property.Value, originalValue, propertyInfo);
? property.Value is IEnumerable enumerable && propertyInfo.TrySetArray(enumerable.Cast<object>(), Global)
: SetValue(property.Value, originalValue, propertyInfo); if(!isChanged) {
continue;
if (!isChanged) continue; }
changedSettings.Add(property.Property); changedSettings.Add(property.Property);
PersistGlobalSettings(); this.PersistGlobalSettings();
} }
return changedSettings; return changedSettings;
} }
/// <summary> /// <summary>
/// Gets the list. /// Gets the list.
/// </summary> /// </summary>
/// <returns>A List of ExtendedPropertyInfo of the type T.</returns> /// <returns>A List of ExtendedPropertyInfo of the type T.</returns>
public List<ExtendedPropertyInfo<T>> GetList() public List<ExtendedPropertyInfo<T>> GetList() {
{ Dictionary<String, Object> jsonData = Json.Deserialize(Json.Serialize(this.Global)) as Dictionary<String, Object>;
var jsonData = Json.Deserialize(Json.Serialize(Global)) as Dictionary<string, object>;
return jsonData?.Keys
return jsonData?.Keys .Select(p => new ExtendedPropertyInfo<T>(p) { Value = jsonData[p] })
.Select(p => new ExtendedPropertyInfo<T>(p) { Value = jsonData[p] }) .ToList();
.ToList(); }
}
/// <summary>
/// <summary> /// Resets the global settings.
/// Resets the global settings. /// </summary>
/// </summary> public void ResetGlobalSettings() {
public void ResetGlobalSettings() lock(this._syncRoot) {
{ this._global = Activator.CreateInstance<T>();
lock (_syncRoot) }
_global = Activator.CreateInstance<T>();
this.PersistGlobalSettings();
PersistGlobalSettings(); }
}
private Boolean SetValue(Object property, Object originalValue, PropertyInfo propertyInfo) {
private bool SetValue(object property, object originalValue, PropertyInfo propertyInfo) switch(property) {
{ case null when originalValue == null:
switch (property) break;
{ case null:
case null when originalValue == null: propertyInfo.SetValue(this.Global, null);
break; return true;
case null: default:
propertyInfo.SetValue(Global, null); if(propertyInfo.PropertyType.TryParseBasicType(property, out Object propertyValue) &&
return true; !propertyValue.Equals(originalValue)) {
default: propertyInfo.SetValue(this.Global, propertyValue);
if (propertyInfo.PropertyType.TryParseBasicType(property, out var propertyValue) && return true;
!propertyValue.Equals(originalValue)) }
{
propertyInfo.SetValue(Global, propertyValue); break;
return true; }
}
return false;
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> /// <summary>
/// Represents a singleton pattern abstract class. /// The static, singleton instance reference.
/// </summary> /// </summary>
/// <typeparam name="T">The type of class.</typeparam> protected static readonly Lazy<T> LazyInstance = new Lazy<T>(
public abstract class SingletonBase<T> : IDisposable valueFactory: () => Activator.CreateInstance(typeof(T), true) as T,
where T : class isThreadSafe: true);
{
/// <summary> private Boolean _isDisposing; // To detect redundant calls
/// The static, singleton instance reference.
/// </summary> /// <summary>
protected static readonly Lazy<T> LazyInstance = new Lazy<T>( /// Gets the instance that this singleton represents.
valueFactory: () => Activator.CreateInstance(typeof(T), true) as T, /// If the instance is null, it is constructed and assigned when this member is accessed.
isThreadSafe: true); /// </summary>
/// <value>
private bool _isDisposing; // To detect redundant calls /// The instance.
/// </value>
/// <summary> public static T Instance => LazyInstance.Value;
/// Gets the instance that this singleton represents.
/// If the instance is null, it is constructed and assigned when this member is accessed. /// <inheritdoc />
/// </summary> public void Dispose() => this.Dispose(true);
/// <value>
/// The instance. /// <summary>
/// </value> /// Releases unmanaged and - optionally - managed resources.
public static T Instance => LazyInstance.Value; /// Call the GC.SuppressFinalize if you override this method and use
/// a non-default class finalizer (destructor).
/// <inheritdoc /> /// </summary>
public void Dispose() => Dispose(true); /// <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(Boolean disposeManaged) {
/// <summary> if(this._isDisposing) {
/// Releases unmanaged and - optionally - managed resources. return;
/// Call the GC.SuppressFinalize if you override this method and use }
/// a non-default class finalizer (destructor).
/// </summary> this._isDisposing = true;
/// <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) // free managed resources
{ if(LazyInstance == null) {
if (_isDisposing) return; return;
}
_isDisposing = true;
try {
// free managed resources IDisposable disposableInstance = LazyInstance.Value as IDisposable;
if (LazyInstance == null) return; disposableInstance?.Dispose();
} catch {
try // swallow
{ }
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},
@ -151,309 +147,304 @@
new Operator {Name = "/", Precedence = 4}, new Operator {Name = "/", Precedence = 4},
new Operator {Name = "\\", Precedence = 4}, new Operator {Name = "\\", Precedence = 4},
new Operator {Name = "^", Precedence = 4}, new Operator {Name = "^", Precedence = 4},
}; };
/// <summary>
/// Shunting the yard.
/// </summary>
/// <param name="includeFunctionStopper">if set to <c>true</c> [include function stopper] (Token type <c>Wall</c>).</param>
/// <returns>
/// Enumerable of the token in in.
/// </returns>
/// <exception cref="InvalidOperationException">
/// Wrong token
/// or
/// Mismatched parenthesis.
/// </exception>
public virtual IEnumerable<Token> ShuntingYard(bool includeFunctionStopper = true)
{
var stack = new Stack<Token>();
foreach (var tok in Tokens)
{
switch (tok.Type)
{
case TokenType.Number:
case TokenType.Variable:
case TokenType.String:
yield return tok;
break;
case TokenType.Function:
stack.Push(tok);
break;
case TokenType.Operator:
while (stack.Any() && stack.Peek().Type == TokenType.Operator &&
CompareOperators(tok.Value, stack.Peek().Value))
yield return stack.Pop();
stack.Push(tok);
break;
case TokenType.Comma:
while (stack.Any() && (stack.Peek().Type != TokenType.Comma &&
stack.Peek().Type != TokenType.Parenthesis))
yield return stack.Pop();
break;
case TokenType.Parenthesis:
if (tok.Value == OpenFuncStr)
{
if (stack.Any() && stack.Peek().Type == TokenType.Function)
{
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");
}
}
while (stack.Any())
{
var tok = stack.Pop();
if (tok.Type == TokenType.Parenthesis)
throw new InvalidOperationException("Mismatched parenthesis");
yield return tok;
}
}
private static bool CompareOperators(Operator op1, Operator op2) => op1.RightAssociative
? op1.Precedence < op2.Precedence
: op1.Precedence <= op2.Precedence;
private void Tokenize(string input)
{
if (!ValidateInput(input, out var startIndex))
{
return;
}
for (var i = startIndex; i < input.Length; i++)
{
if (char.IsWhiteSpace(input, i)) continue;
if (input[i] == CommaChar)
{
Tokens.Add(new Token(TokenType.Comma, new string(new[] { input[i] })));
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(
string input,
int i,
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) =>
ExtractData(input, i, x => TokenType.Operator, x => x == OpenFuncChar ||
x == CommaChar ||
x == PeriodChar ||
x == StringQuotedChar ||
char.IsWhiteSpace(x) ||
char.IsNumber(x));
private int ExtractFunctionOrMember(string input, int i) =>
ExtractData(input, i, ResolveFunctionOrMemberType, x => x == OpenFuncChar ||
x == CloseFuncChar ||
x == CommaChar ||
char.IsWhiteSpace(x));
private int ExtractNumber(string input, int i) =>
ExtractData(input, i, x => TokenType.Number,
x => !char.IsNumber(x) && x != PeriodChar && x != NegativeChar);
private int ExtractString(string input, int i)
{
var length = ExtractData(input, i, x => TokenType.String, x => x == StringQuotedChar, 1, 1);
// open string, report issue
if (length == input.Length && input[length - 1] != StringQuotedChar)
throw new FormatException($"Parser error (Position {i}): Expected '\"' but got '{input[length - 1]}'.");
return length;
}
private bool CompareOperators(string op1, string op2)
=> CompareOperators(GetOperatorOrDefault(op1), GetOperatorOrDefault(op2));
private Operator GetOperatorOrDefault(string op)
=> _operators.FirstOrDefault(x => x.Name == op) ?? new Operator { Name = op, Precedence = 0 };
}
/// <summary> /// <summary>
/// Represents an operator with precedence. /// Shunting the yard.
/// </summary> /// </summary>
public class Operator /// <param name="includeFunctionStopper">if set to <c>true</c> [include function stopper] (Token type <c>Wall</c>).</param>
{ /// <returns>
/// <summary> /// Enumerable of the token in in.
/// Gets or sets the name. /// </returns>
/// </summary> /// <exception cref="InvalidOperationException">
/// <value> /// Wrong token
/// The name. /// or
/// </value> /// Mismatched parenthesis.
public string Name { get; set; } /// </exception>
public virtual IEnumerable<Token> ShuntingYard(Boolean includeFunctionStopper = true) {
/// <summary> Stack<Token> stack = new Stack<Token>();
/// Gets or sets the precedence.
/// </summary> foreach(Token tok in this.Tokens) {
/// <value> switch(tok.Type) {
/// The precedence. case TokenType.Number:
/// </value> case TokenType.Variable:
public int Precedence { get; set; } case TokenType.String:
yield return tok;
/// <summary> break;
/// Gets or sets a value indicating whether [right associative]. case TokenType.Function:
/// </summary> stack.Push(tok);
/// <value> break;
/// <c>true</c> if [right associative]; otherwise, <c>false</c>. case TokenType.Operator:
/// </value> while(stack.Any() && stack.Peek().Type == TokenType.Operator &&
public bool RightAssociative { get; set; } this.CompareOperators(tok.Value, stack.Peek().Value)) {
} yield return stack.Pop();
}
stack.Push(tok);
break;
case TokenType.Comma:
while(stack.Any() && stack.Peek().Type != TokenType.Comma &&
stack.Peek().Type != TokenType.Parenthesis) {
yield return stack.Pop();
}
break;
case TokenType.Parenthesis:
if(tok.Value == OpenFuncStr) {
if(stack.Any() && stack.Peek().Type == TokenType.Function) {
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");
}
}
while(stack.Any()) {
Token tok = stack.Pop();
if(tok.Type == TokenType.Parenthesis) {
throw new InvalidOperationException("Mismatched parenthesis");
}
yield return tok;
}
}
private static Boolean CompareOperators(Operator op1, Operator op2) => op1.RightAssociative
? op1.Precedence < op2.Precedence
: op1.Precedence <= op2.Precedence;
private void Tokenize(String input) {
if(!this.ValidateInput(input, out Int32 startIndex)) {
return;
}
for(Int32 i = startIndex; i < input.Length; i++) {
if(Char.IsWhiteSpace(input, i)) {
continue;
}
if(input[i] == CommaChar) {
this.Tokens.Add(new Token(TokenType.Comma, new String(new[] { input[i] })));
continue;
}
if(input[i] == StringQuotedChar) {
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 ||
Char.IsWhiteSpace(x));
private Int32 ExtractNumber(String input, Int32 i) =>
this.ExtractData(input, i, x => TokenType.Number,
x => !Char.IsNumber(x) && x != PeriodChar && x != NegativeChar);
private Int32 ExtractString(String input, Int32 i) {
Int32 length = this.ExtractData(input, i, x => TokenType.String, x => x == StringQuotedChar, 1, 1);
// open string, report issue
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 Operator GetOperatorOrDefault(String op)
=> this._operators.FirstOrDefault(x => x.Name == op) ?? new Operator { Name = op, Precedence = 0 };
}
/// <summary>
/// Represents an operator with precedence.
/// </summary>
public class Operator {
/// <summary> /// <summary>
/// Represents a Token structure. /// Gets or sets the name.
/// </summary> /// </summary>
public struct Token /// <value>
{ /// The name.
/// <summary> /// </value>
/// Initializes a new instance of the <see cref="Token"/> struct. public String Name {
/// </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>
/// Gets or sets the type.
/// </summary>
/// <value>
/// The type.
/// </value>
public TokenType Type { get; set; }
/// <summary>
/// Gets the value.
/// </summary>
/// <value>
/// The value.
/// </value>
public string Value { get; }
}
/// <summary> /// <summary>
/// Enums the token types. /// Gets or sets the precedence.
/// </summary> /// </summary>
public enum TokenType /// <value>
{ /// The precedence.
/// <summary> /// </value>
/// The number public Int32 Precedence {
/// </summary> get; set;
Number, }
/// <summary> /// <summary>
/// The string /// Gets or sets a value indicating whether [right associative].
/// </summary> /// </summary>
String, /// <value>
/// <c>true</c> if [right associative]; otherwise, <c>false</c>.
/// <summary> /// </value>
/// The variable public Boolean RightAssociative {
/// </summary> get; set;
Variable, }
}
/// <summary>
/// The function /// <summary>
/// </summary> /// Represents a Token structure.
Function, /// </summary>
public struct Token {
/// <summary> /// <summary>
/// The parenthesis /// Initializes a new instance of the <see cref="Token"/> struct.
/// </summary> /// </summary>
Parenthesis, /// <param name="type">The type.</param>
/// <param name="value">The value.</param>
/// <summary> public Token(TokenType type, String value) {
/// The operator this.Type = type;
/// </summary> this.Value = type == TokenType.Function || type == TokenType.Operator ? value.ToLowerInvariant() : value;
Operator, }
/// <summary> /// <summary>
/// The comma /// Gets or sets the type.
/// </summary> /// </summary>
Comma, /// <value>
/// The type.
/// <summary> /// </value>
/// The wall, used to specified the end of argument list of the following function public TokenType Type {
/// </summary> get; set;
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>(); /// <summary>
private readonly bool UseDeferredNotifications; /// Initializes a new instance of the <see cref="ViewModelBase"/> class.
/// </summary>
/// <summary> protected ViewModelBase()
/// Initializes a new instance of the <see cref="ViewModelBase"/> class. : this(false) {
/// </summary> // placeholder
/// <param name="useDeferredNotifications">Set to <c>true</c> to use deferred notifications in the background.</param> }
protected ViewModelBase(bool useDeferredNotifications)
{ /// <summary>
UseDeferredNotifications = useDeferredNotifications; /// Occurs when a property value changes.
} /// </summary>
/// <returns></returns>
/// <summary> public event PropertyChangedEventHandler PropertyChanged;
/// Initializes a new instance of the <see cref="ViewModelBase"/> class.
/// </summary> /// <summary>Checks if a property already matches a desired value. Sets the property and
protected ViewModelBase() /// notifies listeners only when necessary.</summary>
: this(false) /// <typeparam name="T">Type of the property.</typeparam>
{ /// <param name="storage">Reference to a property with both getter and setter.</param>
// placeholder /// <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
/// <summary> /// support CallerMemberName.</param>
/// Occurs when a property value changes. /// <param name="notifyAlso">An rray of property names to notify in addition to notifying the changes on the current property name.</param>
/// </summary> /// <returns>True if the value was changed, false if the existing value matched the
/// <returns></returns> /// desired value.</returns>
public event PropertyChangedEventHandler PropertyChanged; protected Boolean SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = "", String[] notifyAlso = null) {
if(EqualityComparer<T>.Default.Equals(storage, value)) {
/// <summary>Checks if a property already matches a desired value. Sets the property and return false;
/// 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> storage = value;
/// <param name="value">Desired value for the property.</param> this.NotifyPropertyChanged(propertyName, notifyAlso);
/// <param name="propertyName">Name of the property used to notify listeners. This return true;
/// 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> /// <summary>
/// <returns>True if the value was changed, false if the existing value matched the /// Notifies one or more properties changed.
/// desired value.</returns> /// </summary>
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = "", string[] notifyAlso = null) /// <param name="propertyNames">The property names.</param>
{ protected void NotifyPropertyChanged(params String[] propertyNames) => this.NotifyPropertyChanged(null, propertyNames);
if (EqualityComparer<T>.Default.Equals(storage, value))
return false; /// <summary>
/// Notifies one or more properties changed.
storage = value; /// </summary>
NotifyPropertyChanged(propertyName, notifyAlso); /// <param name="mainProperty">The main property.</param>
return true; /// <param name="auxiliaryProperties">The auxiliary properties.</param>
} private void NotifyPropertyChanged(String mainProperty, String[] auxiliaryProperties) {
// Queue property notification
/// <summary> if(String.IsNullOrWhiteSpace(mainProperty) == false) {
/// Notifies one or more properties changed. this.QueuedNotifications[mainProperty] = true;
/// </summary> }
/// <param name="propertyNames">The property names.</param>
protected void NotifyPropertyChanged(params string[] propertyNames) => NotifyPropertyChanged(null, propertyNames); // Set the state for notification properties
if(auxiliaryProperties != null) {
/// <summary> foreach(String property in auxiliaryProperties) {
/// Notifies one or more properties changed. if(String.IsNullOrWhiteSpace(property) == false) {
/// </summary> this.QueuedNotifications[property] = true;
/// <param name="mainProperty">The main property.</param> }
/// <param name="auxiliaryProperties">The auxiliary properties.</param> }
private void NotifyPropertyChanged(string mainProperty, string[] auxiliaryProperties) }
{
// Queue property notification // Depending on operation mode, either fire the notifications in the background
if (string.IsNullOrWhiteSpace(mainProperty) == false) // or fire them immediately
QueuedNotifications[mainProperty] = true; if(this.UseDeferredNotifications) {
_ = Task.Run(() => this.NotifyQueuedProperties());
// Set the state for notification properties } else {
if (auxiliaryProperties != null) this.NotifyQueuedProperties();
{ }
foreach (var property in auxiliaryProperties) }
{
if (string.IsNullOrWhiteSpace(property) == false) /// <summary>
QueuedNotifications[property] = true; /// Notifies the queued properties and resets the property name to a non-queued stated.
} /// </summary>
} private void NotifyQueuedProperties() {
// get a snapshot of property names.
// Depending on operation mode, either fire the notifications in the background String[] propertyNames = this.QueuedNotifications.Keys.ToArray();
// or fire them immediately
if (UseDeferredNotifications) // Iterate through the properties
Task.Run(() => NotifyQueuedProperties()); foreach(String property in propertyNames) {
else // don't notify if we don't have a change
NotifyQueuedProperties(); if(!this.QueuedNotifications[property]) {
} continue;
}
/// <summary>
/// Notifies the queued properties and resets the property name to a non-queued stated. // notify and reset queued state to false
/// </summary> try {
private void NotifyQueuedProperties() this.OnPropertyChanged(property);
{ } finally { this.QueuedNotifications[property] = false; }
// get a snapshot of property names. }
var propertyNames = QueuedNotifications.Keys.ToArray(); }
// Iterate through the properties /// <summary>
foreach (var property in propertyNames) /// Called when a property changes its backing value.
{ /// </summary>
// don't notify if we don't have a change /// <param name="propertyName">Name of the property.</param>
if (!QueuedNotifications[property]) continue; private void OnPropertyChanged(String propertyName) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName ?? String.Empty));
// 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));
}
} }

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) /// <inheritdoc/>
: base(initialValue ? 1 : 0) protected override Boolean FromLong(Int64 backingValue) => backingValue != 0;
{
// placeholder /// <inheritdoc/>
} protected override Int64 ToLong(Boolean value) => value ? 1 : 0;
}
/// <inheritdoc/>
protected override bool FromLong(long backingValue) => backingValue != 0;
/// <inheritdoc/>
protected override long ToLong(bool 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) /// <inheritdoc/>
: base(BitConverter.DoubleToInt64Bits(initialValue)) protected override Double FromLong(Int64 backingValue) =>
{ BitConverter.Int64BitsToDouble(backingValue);
// placeholder
} /// <inheritdoc/>
protected override Int64 ToLong(Double value) =>
/// <inheritdoc/> BitConverter.DoubleToInt64Bits(value);
protected override double FromLong(long backingValue) => }
BitConverter.Int64BitsToDouble(backingValue);
/// <inheritdoc/>
protected override long 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) /// <inheritdoc/>
: base(Convert.ToInt64(initialValue)) protected override Int32 FromLong(Int64 backingValue) =>
{ Convert.ToInt32(backingValue);
// placeholder
} /// <inheritdoc/>
protected override Int64 ToLong(Int32 value) =>
/// <inheritdoc/> Convert.ToInt64(value);
protected override int FromLong(long backingValue) => }
Convert.ToInt32(backingValue);
/// <inheritdoc/>
protected override long ToLong(int 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) /// <inheritdoc />
: base(initialValue) protected override Int64 FromLong(Int64 backingValue) => backingValue;
{
// placeholder /// <inheritdoc />
} protected override Int64 ToLong(Int64 value) => value;
}
/// <inheritdoc />
protected override long FromLong(long backingValue) => backingValue;
/// <inheritdoc />
protected override long ToLong(long 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> /// <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.). /// The default long name will be inferred from target property.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Property)] public ArgumentOptionAttribute()
public sealed class ArgumentOptionAttribute : this(String.Empty, String.Empty) {
: Attribute }
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class. /// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class.
/// The default long name will be inferred from target property. /// </summary>
/// </summary> /// <param name="longName">The long name of the option.</param>
public ArgumentOptionAttribute() public ArgumentOptionAttribute(String longName)
: this(string.Empty, string.Empty) : this(String.Empty, longName) {
{ }
}
/// <summary>
/// <summary> /// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class.
/// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class. /// </summary>
/// </summary> /// <param name="shortName">The short name of the option.</param>
/// <param name="longName">The long name of the option.</param> /// <param name="longName">The long name of the option or null if not used.</param>
public ArgumentOptionAttribute(string longName) public ArgumentOptionAttribute(Char shortName, String longName)
: this(string.Empty, longName) : this(new String(shortName, 1), longName) {
{ }
}
/// <summary>
/// <summary> /// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class.
/// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class. /// </summary>
/// </summary> /// <param name="shortName">The short name of the option..</param>
/// <param name="shortName">The short name of the option.</param> public ArgumentOptionAttribute(Char shortName)
/// <param name="longName">The long name of the option or null if not used.</param> : this(new String(shortName, 1), String.Empty) {
public ArgumentOptionAttribute(char shortName, string longName) }
: this(new string(shortName, 1), longName)
{ private ArgumentOptionAttribute(String shortName, String longName) {
} this.ShortName = shortName ?? throw new ArgumentNullException(nameof(shortName));
this.LongName = longName ?? throw new ArgumentNullException(nameof(longName));
/// <summary> }
/// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class.
/// </summary> /// <summary>
/// <param name="shortName">The short name of the option..</param> /// Gets long name of this command line option. This name is usually a single English word.
public ArgumentOptionAttribute(char shortName) /// </summary>
: this(new string(shortName, 1), string.Empty) /// <value>
{ /// The long name.
} /// </value>
public String LongName {
private ArgumentOptionAttribute(string shortName, string longName) get;
{ }
ShortName = shortName ?? throw new ArgumentNullException(nameof(shortName));
LongName = longName ?? throw new ArgumentNullException(nameof(longName)); /// <summary>
} /// Gets a short name of this command line option, made of one character.
/// </summary>
/// <summary> /// <value>
/// Gets long name of this command line option. This name is usually a single English word. /// The short name.
/// </summary> /// </value>
/// <value> public String ShortName {
/// The long name. get;
/// </value> }
public string LongName { get; }
/// <summary>
/// <summary> /// When applying attribute to <see cref="System.Collections.Generic.IEnumerable{T}"/> target properties,
/// Gets a short name of this command line option, made of one character. /// it allows you to split an argument and consume its content as a sequence.
/// </summary> /// </summary>
/// <value> public Char Separator { get; set; } = '\0';
/// The short name.
/// </value> /// <summary>
public string ShortName { get; } /// Gets or sets mapped property default value.
/// </summary>
/// <summary> /// <value>
/// When applying attribute to <see cref="System.Collections.Generic.IEnumerable{T}"/> target properties, /// The default value.
/// it allows you to split an argument and consume its content as a sequence. /// </value>
/// </summary> public Object DefaultValue {
public char Separator { get; set; } = '\0'; get; set;
}
/// <summary>
/// Gets or sets mapped property default value. /// <summary>
/// </summary> /// Gets or sets a value indicating whether a command line option is required.
/// <value> /// </summary>
/// The default value. /// <value>
/// </value> /// <c>true</c> if required; otherwise, <c>false</c>.
public object DefaultValue { get; set; } /// </value>
public Boolean Required {
/// <summary> get; set;
/// Gets or sets a value indicating whether a command line option is required. }
/// </summary>
/// <value> /// <summary>
/// <c>true</c> if required; otherwise, <c>false</c>. /// Gets or sets a short description of this command line option. Usually a sentence summary.
/// </value> /// </summary>
public bool Required { get; set; } /// <value>
/// The help text.
/// <summary> /// </value>
/// Gets or sets a short description of this command line option. Usually a sentence summary. public String HelpText {
/// </summary> get; set;
/// <value> }
/// The help text. }
/// </value>
public string HelpText { get; set; }
}
} }

View File

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

View File

@ -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> /// <summary>
/// An attribute used to help setup a property behavior when serialize/deserialize JSON. /// Initializes a new instance of the <see cref="JsonPropertyAttribute" /> class.
/// </summary> /// </summary>
/// <seealso cref="Attribute" /> /// <param name="propertyName">Name of the property.</param>
[AttributeUsage(AttributeTargets.Property)] /// <param name="ignored">if set to <c>true</c> [ignored].</param>
public sealed class JsonPropertyAttribute : Attribute public JsonPropertyAttribute(String propertyName, Boolean ignored = false) {
{ this.PropertyName = propertyName ?? throw new ArgumentNullException(nameof(propertyName));
/// <summary> this.Ignored = ignored;
/// Initializes a new instance of the <see cref="JsonPropertyAttribute" /> class. }
/// </summary>
/// <param name="propertyName">Name of the property.</param> /// <summary>
/// <param name="ignored">if set to <c>true</c> [ignored].</param> /// Gets or sets the name of the property.
public JsonPropertyAttribute(string propertyName, bool ignored = false) /// </summary>
{ /// <value>
PropertyName = propertyName ?? throw new ArgumentNullException(nameof(propertyName)); /// The name of the property.
Ignored = ignored; /// </value>
} public String PropertyName {
get;
/// <summary> }
/// Gets or sets the name of the property.
/// </summary> /// <summary>
/// <value> /// Gets or sets a value indicating whether this <see cref="JsonPropertyAttribute" /> is ignored.
/// The name of the property. /// </summary>
/// </value> /// <value>
public string PropertyName { get; } /// <c>true</c> if ignored; otherwise, <c>false</c>.
/// </value>
/// <summary> public Boolean Ignored {
/// Gets or sets a value indicating whether this <see cref="JsonPropertyAttribute" /> is ignored. get;
/// </summary> }
/// <value> }
/// <c>true</c> if ignored; otherwise, <c>false</c>.
/// </value>
public bool 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> /// <summary>
/// An attribute used to include additional information to a Property for serialization. /// Gets or sets the name.
///
/// Previously we used DisplayAttribute from DataAnnotation.
/// </summary> /// </summary>
/// <seealso cref="System.Attribute" /> /// <value>
[AttributeUsage(AttributeTargets.Property)] /// The name.
public sealed class PropertyDisplayAttribute : Attribute /// </value>
{ public String Name {
/// <summary> get; set;
/// Gets or sets the name. }
/// </summary>
/// <value> /// <summary>
/// The name. /// Gets or sets the description.
/// </value> /// </summary>
public string Name { get; set; } /// <value>
/// The description.
/// <summary> /// </value>
/// Gets or sets the description. public String Description {
/// </summary> get; set;
/// <value> }
/// The description.
/// </value> /// <summary>
public string Description { get; set; } /// Gets or sets the name of the group.
/// </summary>
/// <summary> /// <value>
/// Gets or sets the name of the group. /// The name of the group.
/// </summary> /// </value>
/// <value> public String GroupName {
/// The name of the group. get; set;
/// </value> }
public string GroupName { get; set; }
/// <summary>
/// <summary> /// Gets or sets the default value.
/// Gets or sets the default value. /// </summary>
/// </summary> /// <value>
/// <value> /// The default value.
/// The default value. /// </value>
/// </value> public Object DefaultValue {
public object DefaultValue { get; set; } get; set;
}
/// <summary>
/// Gets or sets the format string to call with method <c>ToString</c>. /// <summary>
/// </summary> /// Gets or sets the format string to call with method <c>ToString</c>.
/// <value> /// </summary>
/// The format. /// <value>
/// </value> /// The format.
public string Format { get; set; } /// </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> /// <summary>
/// An attribute used to help conversion structs back and forth into arrays of bytes via /// Initializes a new instance of the <see cref="StructEndiannessAttribute"/> class.
/// extension methods included in this library ToStruct and ToBytes.
/// </summary> /// </summary>
/// <seealso cref="Attribute" /> /// <param name="endianness">The endianness.</param>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Struct)] public StructEndiannessAttribute(Endianness endianness) => this.Endianness = endianness;
public class StructEndiannessAttribute : Attribute
{ /// <summary>
/// <summary> /// Gets the endianness.
/// Initializes a new instance of the <see cref="StructEndiannessAttribute"/> class. /// </summary>
/// </summary> /// <value>
/// <param name="endianness">The endianness.</param> /// The endianness.
public StructEndiannessAttribute(Endianness endianness) /// </value>
{ public Endianness Endianness {
Endianness = endianness; get;
} }
}
/// <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> /// <summary>
/// Regex validator. /// Initializes a new instance of the <see cref="MatchAttribute" /> class.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Property)] /// <param name="regex">A regex string.</param>
public class MatchAttribute : Attribute, IValidator /// <param name="errorMessage">The error message.</param>
{ /// <exception cref="ArgumentNullException">Expression.</exception>
/// <summary> public MatchAttribute(String regex, String errorMessage = null) {
/// Initializes a new instance of the <see cref="MatchAttribute" /> class. this.Expression = regex ?? throw new ArgumentNullException(nameof(this.Expression));
/// </summary> this.ErrorMessage = errorMessage ?? "String does not match the specified regular expression";
/// <param name="regex">A regex string.</param> }
/// <param name="errorMessage">The error message.</param>
/// <exception cref="ArgumentNullException">Expression.</exception> /// <summary>
public MatchAttribute(string regex, string errorMessage = null) /// The string regex used to find a match.
{ /// </summary>
Expression = regex ?? throw new ArgumentNullException(nameof(Expression)); public String Expression {
ErrorMessage = errorMessage ?? "String does not match the specified regular expression"; get;
} }
/// <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> /// <summary>
/// Email validator. /// Initializes a new instance of the <see cref="EmailAttribute"/> class.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Property)] /// <param name="errorMessage">The error message.</param>
public class EmailAttribute : MatchAttribute public EmailAttribute(String errorMessage = null)
{ : base(EmailRegExp, errorMessage ?? "String is not an email") {
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>
/// <summary> /// A not null validator.
/// Initializes a new instance of the <see cref="EmailAttribute"/> class. /// </summary>
/// </summary> [AttributeUsage(AttributeTargets.Property)]
/// <param name="errorMessage">The error message.</param> public class NotNullAttribute : Attribute, IValidator {
public EmailAttribute(string errorMessage = null) /// <inheritdoc/>
: base(EmailRegExp, errorMessage ?? "String is not an email") 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> /// <summary>
/// A not null validator. /// Initializes a new instance of the <see cref="RangeAttribute"/> class.
/// Constructor that takes integer minimum and maximum values.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Property)] /// <param name="min">The minimum value.</param>
public class NotNullAttribute : Attribute, IValidator /// <param name="max">The maximum value.</param>
{ public RangeAttribute(Int32 min, Int32 max) {
/// <inheritdoc/> if(min >= max) {
public string ErrorMessage => "Value is null"; throw new InvalidOperationException("Maximum value must be greater than minimum");
}
/// <inheritdoc/>
public bool IsValid<T>(T value) => !Equals(default(T), value); this.Maximum = max;
} this.Minimum = min;
}
/// <summary> /// <summary>
/// A range constraint 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 RangeAttribute : Attribute, IValidator /// <param name="max">The maximum value.</param>
{ public RangeAttribute(Double min, Double max) {
/// <summary> if(min >= max) {
/// Initializes a new instance of the <see cref="RangeAttribute"/> class. throw new InvalidOperationException("Maximum value must be greater than minimum");
/// Constructor that takes integer minimum and maximum values. }
/// </summary>
/// <param name="min">The minimum value.</param> this.Maximum = max;
/// <param name="max">The maximum value.</param> this.Minimum = min;
public RangeAttribute(int min, int max) }
{
if (min >= max) /// <inheritdoc/>
throw new InvalidOperationException("Maximum value must be greater than minimum"); public String ErrorMessage => "Value is not within the specified range";
Maximum = max; /// <summary>
Minimum = min; /// Maximum value for the range.
} /// </summary>
public IComparable Maximum {
/// <summary> get;
/// Initializes a new instance of the <see cref="RangeAttribute"/> class. }
/// Constructor that takes double minimum and maximum values.
/// </summary> /// <summary>
/// <param name="min">The minimum value.</param> /// Minimum value for the range.
/// <param name="max">The maximum value.</param> /// </summary>
public RangeAttribute(double min, double max) public IComparable Minimum {
{ get;
if (min >= max) }
throw new InvalidOperationException("Maximum value must be greater than minimum");
/// <inheritdoc/>
Maximum = max; public Boolean IsValid<T>(T value)
Minimum = min; => value is IComparable comparable
} ? comparable.CompareTo(this.Minimum) >= 0 && comparable.CompareTo(this.Maximum) <= 0
: throw new ArgumentException(nameof(value));
/// <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> /// <summary>
/// Models a verb option. /// Initializes a new instance of the <see cref="VerbOptionAttribute" /> class.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Property)] /// <param name="name">The name.</param>
public sealed class VerbOptionAttribute : Attribute /// <exception cref="ArgumentNullException">name.</exception>
{ public VerbOptionAttribute(String name) => this.Name = name ?? throw new ArgumentNullException(nameof(name));
/// <summary>
/// Initializes a new instance of the <see cref="VerbOptionAttribute" /> class. /// <summary>
/// </summary> /// Gets the name of the verb option.
/// <param name="name">The name.</param> /// </summary>
/// <exception cref="ArgumentNullException">name.</exception> /// <value>
public VerbOptionAttribute(string name) /// Name.
{ /// </value>
Name = name ?? throw new ArgumentNullException(nameof(name)); public String Name {
} get;
}
/// <summary>
/// Gets the name of the verb option. /// <summary>
/// </summary> /// Gets or sets a short description of this command line verb. Usually a sentence summary.
/// <value> /// </summary>
/// Name. /// <value>
/// </value> /// The help text.
public string Name { get; } /// </value>
public String HelpText {
/// <summary> get; set;
/// Gets or sets a short description of this command line verb. Usually a sentence summary. }
/// </summary>
/// <value> /// <inheritdoc />
/// The help text. public override String ToString() => $" {this.Name}\t\t{this.HelpText}";
/// </value> }
public string HelpText { get; set; }
/// <inheritdoc />
public override string ToString() => $" {Name}\t\t{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; namespace Unosquare.Swan.Components {
/// <summary>
/// <summary> /// Provides methods to parse command line arguments.
/// Provides methods to parse command line arguments. /// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.).
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.). /// </summary>
/// </summary> public partial class ArgumentParser {
public partial class ArgumentParser private sealed class Validator {
{ private readonly Object _instance;
private sealed class Validator private readonly IEnumerable<String> _args;
{ private readonly List<PropertyInfo> _updatedList = new List<PropertyInfo>();
private readonly object _instance; private readonly ArgumentParserSettings _settings;
private readonly IEnumerable<string> _args;
private readonly List<PropertyInfo> _updatedList = new List<PropertyInfo>(); private readonly PropertyInfo[] _properties;
private readonly ArgumentParserSettings _settings;
public Validator(
private readonly PropertyInfo[] _properties; PropertyInfo[] properties,
IEnumerable<String> args,
public Validator( Object instance,
PropertyInfo[] properties, ArgumentParserSettings settings) {
IEnumerable<string> args, this._args = args;
object instance, this._instance = instance;
ArgumentParserSettings settings) this._settings = settings;
{ this._properties = properties;
_args = args;
_instance = instance; this.PopulateInstance();
_settings = settings; this.SetDefaultValues();
_properties = properties; this.GetRequiredList();
}
PopulateInstance();
SetDefaultValues(); public List<String> UnknownList { get; } = new List<String>();
GetRequiredList(); public List<String> RequiredList { get; } = new List<String>();
}
public Boolean IsValid() => (this._settings.IgnoreUnknownArguments || !this.UnknownList.Any()) && !this.RequiredList.Any();
public List<string> UnknownList { get; } = new List<string>();
public List<string> RequiredList { get; } = new List<string>(); public IEnumerable<ArgumentOptionAttribute> GetPropertiesOptions()
=> this._properties.Select(p => Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(p))
public bool IsValid() => (_settings.IgnoreUnknownArguments || !UnknownList.Any()) && !RequiredList.Any(); .Where(x => x != null);
public IEnumerable<ArgumentOptionAttribute> GetPropertiesOptions() private void GetRequiredList() {
=> _properties.Select(p => Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(p)) foreach(PropertyInfo targetProperty in this._properties) {
.Where(x => x != null); ArgumentOptionAttribute optionAttr = Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(targetProperty);
private void GetRequiredList() if(optionAttr == null || optionAttr.Required == false) {
{ continue;
foreach (var targetProperty in _properties) }
{
var optionAttr = Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(targetProperty); if(targetProperty.GetValue(this._instance) == null) {
this.RequiredList.Add(optionAttr.LongName ?? optionAttr.ShortName);
if (optionAttr == null || optionAttr.Required == false) }
continue; }
}
if (targetProperty.GetValue(_instance) == null)
{ private void SetDefaultValues() {
RequiredList.Add(optionAttr.LongName ?? optionAttr.ShortName); foreach(PropertyInfo targetProperty in this._properties.Except(this._updatedList)) {
} ArgumentOptionAttribute optionAttr = Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(targetProperty);
}
} Object defaultValue = optionAttr?.DefaultValue;
private void SetDefaultValues() if(defaultValue == null) {
{ continue;
foreach (var targetProperty in _properties.Except(_updatedList)) }
{
var optionAttr = Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(targetProperty); if(this.SetPropertyValue(targetProperty, defaultValue.ToString(), this._instance, optionAttr)) {
this._updatedList.Add(targetProperty);
var defaultValue = optionAttr?.DefaultValue; }
}
if (defaultValue == null) }
continue;
private void PopulateInstance() {
if (SetPropertyValue(targetProperty, defaultValue.ToString(), _instance, optionAttr)) const Char dash = '-';
_updatedList.Add(targetProperty); String propertyName = String.Empty;
}
} foreach(String arg in this._args) {
Boolean ignoreSetValue = String.IsNullOrWhiteSpace(propertyName);
private void PopulateInstance()
{ if(ignoreSetValue) {
const char dash = '-'; if(String.IsNullOrWhiteSpace(arg) || arg[0] != dash) {
var propertyName = string.Empty; continue;
}
foreach (var arg in _args)
{ propertyName = arg.Substring(1);
var ignoreSetValue = string.IsNullOrWhiteSpace(propertyName);
if(!String.IsNullOrWhiteSpace(propertyName) && propertyName[0] == dash) {
if (ignoreSetValue) propertyName = propertyName.Substring(1);
{ }
if (string.IsNullOrWhiteSpace(arg) || arg[0] != dash) continue; }
propertyName = arg.Substring(1); PropertyInfo targetProperty = this.TryGetProperty(propertyName);
if (!string.IsNullOrWhiteSpace(propertyName) && propertyName[0] == dash) if(targetProperty == null) {
propertyName = propertyName.Substring(1); // Skip if the property is not found
} this.UnknownList.Add(propertyName);
continue;
var targetProperty = TryGetProperty(propertyName); }
if (targetProperty == null) if(!ignoreSetValue && this.SetPropertyValue(targetProperty, arg, this._instance)) {
{ this._updatedList.Add(targetProperty);
// Skip if the property is not found propertyName = String.Empty;
UnknownList.Add(propertyName); } else if(targetProperty.PropertyType == typeof(Boolean)) {
continue; // If the arg is a boolean property set it to true.
} targetProperty.SetValue(this._instance, true);
if (!ignoreSetValue && SetPropertyValue(targetProperty, arg, _instance)) this._updatedList.Add(targetProperty);
{ propertyName = String.Empty;
_updatedList.Add(targetProperty); }
propertyName = string.Empty; }
}
else if (targetProperty.PropertyType == typeof(bool)) if(!String.IsNullOrEmpty(propertyName)) {
{ this.UnknownList.Add(propertyName);
// If the arg is a boolean property set it to true. }
targetProperty.SetValue(_instance, true); }
_updatedList.Add(targetProperty); private Boolean SetPropertyValue(
propertyName = string.Empty; PropertyInfo targetProperty,
} String propertyValueString,
} Object result,
ArgumentOptionAttribute optionAttr = null) {
if (!string.IsNullOrEmpty(propertyName)) if(targetProperty.PropertyType.GetTypeInfo().IsEnum) {
{ Object parsedValue = Enum.Parse(
UnknownList.Add(propertyName); targetProperty.PropertyType,
} propertyValueString,
} this._settings.CaseInsensitiveEnumValues);
private bool SetPropertyValue( targetProperty.SetValue(result, Enum.ToObject(targetProperty.PropertyType, parsedValue));
PropertyInfo targetProperty,
string propertyValueString, return true;
object result, }
ArgumentOptionAttribute optionAttr = null)
{ return targetProperty.PropertyType.IsArray
if (targetProperty.PropertyType.GetTypeInfo().IsEnum) ? targetProperty.TrySetArray(propertyValueString.Split(optionAttr?.Separator ?? ','), result)
{ : targetProperty.TrySetBasicType(propertyValueString, result);
var parsedValue = Enum.Parse( }
targetProperty.PropertyType,
propertyValueString, private PropertyInfo TryGetProperty(String propertyName)
_settings.CaseInsensitiveEnumValues); => this._properties.FirstOrDefault(p =>
String.Equals(Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(p)?.LongName, propertyName, this._settings.NameComparer) ||
targetProperty.SetValue(result, Enum.ToObject(targetProperty.PropertyType, parsedValue)); String.Equals(Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(p)?.ShortName, propertyName, this._settings.NameComparer));
}
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));
}
}
} }

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; namespace Unosquare.Swan.Components {
/// <summary>
/// <summary> /// Provides methods to parse command line arguments.
/// Provides methods to parse command line arguments. /// </summary>
/// </summary> public partial class ArgumentParser {
public partial class ArgumentParser private sealed class TypeResolver<T> {
{ private readonly String _selectedVerb;
private sealed class TypeResolver<T>
{ private PropertyInfo[] _properties;
private readonly string _selectedVerb;
public TypeResolver(String selectedVerb) => this._selectedVerb = selectedVerb;
private PropertyInfo[] _properties;
public PropertyInfo[] GetProperties() => this._properties?.Any() == true ? this._properties : null;
public TypeResolver(string selectedVerb)
{ public Object GetOptionsObject(T instance) {
_selectedVerb = selectedVerb; this._properties = Runtime.PropertyTypeCache.RetrieveAllProperties<T>(true).ToArray();
}
if(!this._properties.Any(x => x.GetCustomAttributes(typeof(VerbOptionAttribute), false).Any())) {
public PropertyInfo[] GetProperties() => _properties?.Any() == true ? _properties : null; return instance;
}
public object GetOptionsObject(T instance)
{ PropertyInfo selectedVerb = String.IsNullOrWhiteSpace(this._selectedVerb)
_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 ? 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(); }
var verbProperty = type.GetProperty(selectedVerb.Name); Type type = instance.GetType();
if (verbProperty?.GetValue(instance) == null) PropertyInfo verbProperty = type.GetProperty(selectedVerb.Name);
{
var propertyInstance = Activator.CreateInstance(selectedVerb.PropertyType); if(verbProperty?.GetValue(instance) == null) {
verbProperty?.SetValue(instance, propertyInstance); Object propertyInstance = Activator.CreateInstance(selectedVerb.PropertyType);
} verbProperty?.SetValue(instance, propertyInstance);
}
_properties = Runtime.PropertyTypeCache.RetrieveAllProperties(selectedVerb.PropertyType, true)
.ToArray(); this._properties = Runtime.PropertyTypeCache.RetrieveAllProperties(selectedVerb.PropertyType, true)
.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> /// <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.).
/// </summary> /// </summary>
/// <example> public ArgumentParser()
/// The following example shows how to parse CLI arguments into objects. : this(new ArgumentParserSettings()) {
/// <code> }
/// class Example
/// { /// <summary>
/// using System; /// Initializes a new instance of the <see cref="ArgumentParser" /> class,
/// using Unosquare.Swan; /// configurable with <see cref="ArgumentParserSettings" /> using a delegate.
/// using Unosquare.Swan.Attributes; /// </summary>
/// /// <param name="parseSettings">The parse settings.</param>
/// static void Main(string[] args) public ArgumentParser(ArgumentParserSettings parseSettings) => this.Settings = parseSettings ?? throw new ArgumentNullException(nameof(parseSettings));
/// {
/// // create an instance of the Options class /// <summary>
/// var options = new Options(); /// Gets the instance that implements <see cref="ArgumentParserSettings" /> in use.
/// /// </summary>
/// // parse the supplied command-line arguments into the options object /// <value>
/// var res = Runtime.ArgumentParser.ParseArguments(args, options); /// The settings.
/// } /// </value>
/// public ArgumentParserSettings Settings {
/// class Options get;
/// { }
/// [ArgumentOption('v', "verbose", HelpText = "Set verbose mode.")]
/// public bool Verbose { get; set; } /// <summary>
/// /// Parses a string array of command line arguments constructing values in an instance of type <typeparamref name="T" />.
/// [ArgumentOption('u', Required = true, HelpText = "Set user name.")] /// </summary>
/// public string Username { get; set; } /// <typeparam name="T">The type of the options.</typeparam>
/// /// <param name="args">The arguments.</param>
/// [ArgumentOption('n', "names", Separator = ',', /// <param name="instance">The instance.</param>
/// Required = true, HelpText = "A list of files separated by a comma")] /// <returns>
/// public string[] Files { get; set; } /// <c>true</c> if was converted successfully; otherwise, <c>false</c>.
/// /// </returns>
/// [ArgumentOption('p', "port", DefaultValue = 22, HelpText = "Set port.")] /// <exception cref="ArgumentNullException">
/// public int Port { get; set; } /// 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.
/// [ArgumentOption("color", DefaultValue = ConsoleColor.Red, /// </exception>
/// HelpText = "Set a color.")] /// <exception cref="InvalidOperationException">
/// public ConsoleColor Color { get; set; } /// 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) {
/// </code> if(args == null) {
/// The following code describes how to parse CLI verbs. throw new ArgumentNullException(nameof(args));
/// <code> }
/// class Example2
/// { if(Equals(instance, default(T))) {
/// using Unosquare.Swan; throw new ArgumentNullException(nameof(instance));
/// using Unosquare.Swan.Attributes; }
///
/// static void Main(string[] args) TypeResolver<T> typeResolver = new TypeResolver<T>(args.FirstOrDefault());
/// { Object options = typeResolver.GetOptionsObject(instance);
/// // create an instance of the VerbOptions class
/// var options = new VerbOptions(); if(options == null) {
/// ReportUnknownVerb<T>();
/// // parse the supplied command-line arguments into the options object return false;
/// var res = Runtime.ArgumentParser.ParseArguments(args, options); }
///
/// // if there were no errors parsing System.Reflection.PropertyInfo[] properties = typeResolver.GetProperties();
/// if (res)
/// { if(properties == null) {
/// if(options.Run != null) throw new InvalidOperationException($"Type {typeof(T).Name} is not valid");
/// { }
/// // run verb was selected
/// } Validator validator = new Validator(properties, args, options, this.Settings);
///
/// if(options.Print != null) if(validator.IsValid()) {
/// { return true;
/// // print verb was selected }
/// }
/// } this.ReportIssues(validator);
/// return false;
/// // flush all error messages }
/// Terminal.Flush();
/// } private static void ReportUnknownVerb<T>() {
/// "No verb was specified".WriteLine(ConsoleColor.Red);
/// class VerbOptions "Valid verbs:".WriteLine(ConsoleColor.Cyan);
/// {
/// [VerbOption("run", HelpText = "Run verb.")] Runtime.PropertyTypeCache.RetrieveAllProperties<T>(true)
/// public RunVerbOption Run { get; set; } .Select(x => Runtime.AttributeCache.RetrieveOne<VerbOptionAttribute>(x))
/// .Where(x => x != null)
/// [VerbOption("print", HelpText = "Print verb.")] .ToList()
/// public PrintVerbOption Print { get; set; } .ForEach(x => x.ToString().WriteLine(ConsoleColor.Cyan));
/// } }
///
/// class RunVerbOption private void ReportIssues(Validator validator) {
/// { #if !NETSTANDARD1_3
/// [ArgumentOption('o', "outdir", HelpText = "Output directory", if(this.Settings.WriteBanner) {
/// DefaultValue = "", Required = false)] Runtime.WriteWelcomeBanner();
/// 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 #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 String shortName = String.IsNullOrWhiteSpace(option.ShortName) ? String.Empty : $"-{option.ShortName}";
var shortName = string.IsNullOrWhiteSpace(option.ShortName) ? string.Empty : $"-{option.ShortName}"; String longName = String.IsNullOrWhiteSpace(option.LongName) ? String.Empty : $"--{option.LongName}";
var longName = string.IsNullOrWhiteSpace(option.LongName) ? string.Empty : $"--{option.LongName}"; String comma = String.IsNullOrWhiteSpace(shortName) || String.IsNullOrWhiteSpace(longName)
var comma = string.IsNullOrWhiteSpace(shortName) || string.IsNullOrWhiteSpace(longName) ? String.Empty
? string.Empty : ", ";
: ", "; String defaultValue = option.DefaultValue == null ? String.Empty : $"(Default: {option.DefaultValue}) ";
var defaultValue = option.DefaultValue == null ? string.Empty : $"(Default: {option.DefaultValue}) ";
$" {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> /// <summary>
/// Provides settings for <see cref="ArgumentParser"/>. /// Gets or sets a value indicating whether [write banner].
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.).
/// </summary> /// </summary>
public class ArgumentParserSettings /// <value>
{ /// <c>true</c> if [write banner]; otherwise, <c>false</c>.
/// <summary> /// </value>
/// Gets or sets a value indicating whether [write banner]. public Boolean WriteBanner { get; set; } = true;
/// </summary>
/// <value> /// <summary>
/// <c>true</c> if [write banner]; otherwise, <c>false</c>. /// Gets or sets a value indicating whether perform case sensitive comparisons.
/// </value> /// Note that case insensitivity only applies to <i>parameters</i>, not the values
public bool WriteBanner { get; set; } = true; /// assigned to them (for example, enum parsing).
/// </summary>
/// <summary> /// <value>
/// Gets or sets a value indicating whether perform case sensitive comparisons. /// <c>true</c> if [case sensitive]; otherwise, <c>false</c>.
/// Note that case insensitivity only applies to <i>parameters</i>, not the values /// </value>
/// assigned to them (for example, enum parsing). public Boolean CaseSensitive { get; set; } = false;
/// </summary>
/// <value> /// <summary>
/// <c>true</c> if [case sensitive]; otherwise, <c>false</c>. /// Gets or sets a value indicating whether perform case sensitive comparisons of <i>values</i>.
/// </value> /// Note that case insensitivity only applies to <i>values</i>, not the parameters.
public bool CaseSensitive { get; set; } = false; /// </summary>
/// <value>
/// <summary> /// <c>true</c> if [case insensitive enum values]; otherwise, <c>false</c>.
/// Gets or sets a value indicating whether perform case sensitive comparisons of <i>values</i>. /// </value>
/// Note that case insensitivity only applies to <i>values</i>, not the parameters. public Boolean CaseInsensitiveEnumValues { get; set; } = true;
/// </summary>
/// <value> /// <summary>
/// <c>true</c> if [case insensitive enum values]; otherwise, <c>false</c>. /// Gets or sets a value indicating whether the parser shall move on to the next argument and ignore the given argument if it
/// </value> /// encounter an unknown arguments.
public bool CaseInsensitiveEnumValues { get; set; } = true; /// </summary>
/// <value>
/// <summary> /// <c>true</c> to allow parsing the arguments with different class options that do not have all the arguments.
/// Gets or sets a value indicating whether the parser shall move on to the next argument and ignore the given argument if it /// </value>
/// encounter an unknown arguments. /// <remarks>
/// </summary> /// This allows fragmented version class parsing, useful for project with add-on where add-ons also requires command line arguments but
/// <value> /// when these are unknown by the main program at build time.
/// <c>true</c> to allow parsing the arguments with different class options that do not have all the arguments. /// </remarks>
/// </value> public Boolean IgnoreUnknownArguments { 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 internal StringComparison NameComparer => this.CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
/// 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
/// { /// <summary>
/// using Unosquare.Swan.Components; /// Outputs the benchmark statistics.
/// /// </summary>
/// public class SimpleBenchmark /// <returns>A string containing human-readable statistics.</returns>
/// { public static String Dump() {
/// public static void Main() StringBuilder builder = new StringBuilder();
/// {
/// using (Benchmark.Start("Test")) lock(SyncLock) {
/// { foreach(KeyValuePair<String, List<TimeSpan>> kvp in Measures) {
/// // do some logic in here _ = builder.Append($"BID: {kvp.Key,-30} | ")
/// } .Append($"CNT: {kvp.Value.Count,6} | ")
/// .Append($"AVG: {kvp.Value.Average(t => t.TotalMilliseconds),8:0.000} ms. | ")
/// // dump results into a string .Append($"MAX: {kvp.Value.Max(t => t.TotalMilliseconds),8:0.000} ms. | ")
/// var results = Benchmark.Dump(); .Append($"MIN: {kvp.Value.Min(t => t.TotalMilliseconds),8:0.000} ms. | ")
/// } .Append(Environment.NewLine);
/// } }
/// }
/// }
/// </code> return builder.ToString().TrimEnd();
/// </example> }
public static class Benchmark
{ /// <summary>
private static readonly object SyncLock = new object(); /// Adds the specified result to the given identifier.
private static readonly Dictionary<string, List<TimeSpan>> Measures = new Dictionary<string, List<TimeSpan>>(); /// </summary>
/// <param name="identifier">The identifier.</param>
/// <summary> /// <param name="elapsed">The elapsed.</param>
/// Starts measuring with the given identifier. private static void Add(String identifier, TimeSpan elapsed) {
/// </summary> lock(SyncLock) {
/// <param name="identifier">The identifier.</param> if(Measures.ContainsKey(identifier) == false) {
/// <returns>A disposable object that when disposed, adds a benchmark result.</returns> Measures[identifier] = new List<TimeSpan>(1024 * 1024);
public static IDisposable Start(string identifier) => new BenchmarkUnit(identifier); }
/// <summary> Measures[identifier].Add(elapsed);
/// Outputs the benchmark statistics. }
/// </summary> }
/// <returns>A string containing human-readable statistics.</returns>
public static string Dump() /// <summary>
{ /// Represents a disposable benchmark unit.
var builder = new StringBuilder(); /// </summary>
/// <seealso cref="IDisposable" />
lock (SyncLock) private sealed class BenchmarkUnit : IDisposable {
{ private readonly String _identifier;
foreach (var kvp in Measures) private Boolean _isDisposed; // To detect redundant calls
{ private Stopwatch _stopwatch = new Stopwatch();
builder.Append($"BID: {kvp.Key,-30} | ")
.Append($"CNT: {kvp.Value.Count,6} | ") /// <summary>
.Append($"AVG: {kvp.Value.Average(t => t.TotalMilliseconds),8:0.000} ms. | ") /// Initializes a new instance of the <see cref="BenchmarkUnit" /> class.
.Append($"MAX: {kvp.Value.Max(t => t.TotalMilliseconds),8:0.000} ms. | ") /// </summary>
.Append($"MIN: {kvp.Value.Min(t => t.TotalMilliseconds),8:0.000} ms. | ") /// <param name="identifier">The identifier.</param>
.Append(Environment.NewLine); public BenchmarkUnit(String identifier) {
} this._identifier = identifier;
} this._stopwatch.Start();
}
return builder.ToString().TrimEnd();
} /// <inheritdoc />
public void Dispose() => this.Dispose(true);
/// <summary>
/// Adds the specified result to the given identifier. /// <summary>
/// </summary> /// Releases unmanaged and - optionally - managed resources.
/// <param name="identifier">The identifier.</param> /// </summary>
/// <param name="elapsed">The elapsed.</param> /// <param name="alsoManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
private static void Add(string identifier, TimeSpan elapsed) private void Dispose(Boolean alsoManaged) {
{ if(this._isDisposed) {
lock (SyncLock) return;
{ }
if (Measures.ContainsKey(identifier) == false)
Measures[identifier] = new List<TimeSpan>(1024 * 1024); if(alsoManaged) {
Add(this._identifier, this._stopwatch.Elapsed);
Measures[identifier].Add(elapsed); this._stopwatch?.Stop();
} }
}
this._stopwatch = null;
/// <summary> this._isDisposed = true;
/// 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;
}
}
}
} }

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>>>(() => /// <summary>
new ConcurrentDictionary<Type, IEnumerable<TValue>>(), true); /// Retrieves the properties stored for the specified type.
/// If the properties are not available, it calls the factory method to retrieve them
/// <summary> /// and returns them as an array of PropertyInfo.
/// Determines whether the cache contains the specified key. /// </summary>
/// </summary> /// <param name="key">The key.</param>
/// <param name="key">The key.</param> /// <param name="factory">The factory.</param>
/// <returns><c>true</c> if the cache contains the key, otherwise <c>false</c>.</returns> /// <returns>
public bool ContainsKey(Type key) => _data.Value.ContainsKey(key); /// An array of the properties stored for the specified type.
/// </returns>
/// <summary> /// <exception cref="System.ArgumentNullException">type.</exception>
/// Retrieves the properties stored for the specified type. public IEnumerable<TValue> Retrieve(Type key, Func<Type, IEnumerable<TValue>> factory) {
/// If the properties are not available, it calls the factory method to retrieve them if(factory == null) {
/// and returns them as an array of PropertyInfo. throw new ArgumentNullException(nameof(factory));
/// </summary> }
/// <param name="key">The key.</param>
/// <param name="factory">The factory.</param> return this._data.Value.GetOrAdd(key, k => factory.Invoke(k).Where(item => item != null));
/// <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> /// <summary>
/// Provide Enumerations helpers with internal cache. /// Gets all the names and enumerators from a specific Enum type.
/// </summary> /// </summary>
public class EnumHelper /// <typeparam name="T">The type of the attribute to be retrieved.</typeparam>
: SingletonBase<CollectionCacheRepository<Tuple<string, object>>> /// <returns>A tuple of enumerator names and their value stored for the specified type.</returns>
{ public static IEnumerable<Tuple<String, Object>> Retrieve<T>()
/// <summary> where T : struct, IConvertible => Instance.Retrieve(typeof(T), t => Enum.GetValues(t)
/// Gets all the names and enumerators from a specific Enum type. .Cast<Object>()
/// </summary> .Select(item => Tuple.Create(Enum.GetName(t, item), item)));
/// <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> /// <summary>
public static IEnumerable<Tuple<string, object>> Retrieve<T>() /// Gets the cached items with the enum item value.
where T : struct, IConvertible /// </summary>
{ /// <typeparam name="T">The type of enumeration.</typeparam>
return Instance.Retrieve(typeof(T), t => Enum.GetValues(t) /// <param name="humanize">if set to <c>true</c> [humanize].</param>
.Cast<object>() /// <returns>
.Select(item => Tuple.Create(Enum.GetName(t, item), item))); /// A collection of Type/Tuple pairs
} /// that represents items with the enum item value.
/// </returns>
/// <summary> public static IEnumerable<Tuple<Int32, String>> GetItemsWithValue<T>(Boolean humanize = true)
/// Gets the cached items with the enum item value. where T : struct, IConvertible => Retrieve<T>()
/// </summary> .Select(x => Tuple.Create((Int32)x.Item2, humanize ? x.Item1.Humanize() : x.Item1));
/// <typeparam name="T">The type of enumeration.</typeparam>
/// <param name="humanize">if set to <c>true</c> [humanize].</param> /// <summary>
/// <returns> /// Gets the flag values.
/// A collection of Type/Tuple pairs /// </summary>
/// that represents items with the enum item value. /// <typeparam name="TEnum">The type of the enum.</typeparam>
/// </returns> /// <param name="value">The value.</param>
public static IEnumerable<Tuple<int, string>> GetItemsWithValue<T>(bool humanize = true) /// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
where T : struct, IConvertible /// <returns>
{ /// A list of values in the flag.
return Retrieve<T>() /// </returns>
.Select(x => Tuple.Create((int) x.Item2, humanize ? x.Item1.Humanize() : x.Item1)); public static IEnumerable<Int32> GetFlagValues<TEnum>(Int32 value, Boolean ignoreZero = false)
} where TEnum : struct, IConvertible => Retrieve<TEnum>()
.Select(x => (Int32)x.Item2)
/// <summary> .When(() => ignoreZero, q => q.Where(f => f != 0))
/// Gets the flag values. .Where(x => (x & value) == x);
/// </summary>
/// <typeparam name="TEnum">The type of the enum.</typeparam> /// <summary>
/// <param name="value">The value.</param> /// Gets the flag values.
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param> /// </summary>
/// <returns> /// <typeparam name="TEnum">The type of the enum.</typeparam>
/// A list of values in the flag. /// <param name="value">The value.</param>
/// </returns> /// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
public static IEnumerable<int> GetFlagValues<TEnum>(int value, bool ignoreZero = false) /// <returns>
where TEnum : struct, IConvertible /// A list of values in the flag.
{ /// </returns>
return Retrieve<TEnum>() public static IEnumerable<Int64> GetFlagValues<TEnum>(Int64 value, Boolean ignoreZero = false)
.Select(x => (int) x.Item2) where TEnum : struct, IConvertible => Retrieve<TEnum>()
.When(() => ignoreZero, q => q.Where(f => f != 0)) .Select(x => (Int64)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>
} /// Gets the flag names.
/// </summary>
/// <summary> /// <typeparam name="TEnum">The type of the enum.</typeparam>
/// Gets the flag values. /// <param name="value">the value.</param>
/// </summary> /// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
/// <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 list of flag names.
/// <returns> /// </returns>
/// A list of values in the flag. public static IEnumerable<String> GetFlagNames<TEnum>(Int32 value, Boolean ignoreZero = false, Boolean humanize = true)
/// </returns> where TEnum : struct, IConvertible => Retrieve<TEnum>()
public static IEnumerable<byte> GetFlagValues<TEnum>(byte value, bool ignoreZero = false) .When(() => ignoreZero, q => q.Where(f => (Int32)f.Item2 != 0))
where TEnum : struct, IConvertible .Where(x => ((Int32)x.Item2 & value) == (Int32)x.Item2)
{ .Select(x => humanize ? x.Item1.Humanize() : x.Item1);
return Retrieve<TEnum>()
.Select(x => (byte) x.Item2) /// <summary>
.When(() => ignoreZero, q => q.Where(f => f != 0)) /// Gets the flag names.
.Where(x => (x & value) == x); /// </summary>
} /// <typeparam name="TEnum">The type of the enum.</typeparam>
/// <param name="value">The value.</param>
/// <summary> /// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
/// Gets the flag names. /// <param name="humanize">if set to <c>true</c> [humanize].</param>
/// </summary> /// <returns>
/// <typeparam name="TEnum">The type of the enum.</typeparam> /// A list of flag names.
/// <param name="value">the value.</param> /// </returns>
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param> public static IEnumerable<String> GetFlagNames<TEnum>(Int64 value, Boolean ignoreZero = false, Boolean humanize = true)
/// <param name="humanize">if set to <c>true</c> [humanize].</param> where TEnum : struct, IConvertible => Retrieve<TEnum>()
/// <returns> .When(() => ignoreZero, q => q.Where(f => (Int64)f.Item2 != 0))
/// A list of flag names. .Where(x => ((Int64)x.Item2 & value) == (Int64)x.Item2)
/// </returns> .Select(x => humanize ? x.Item1.Humanize() : x.Item1);
public static IEnumerable<string> GetFlagNames<TEnum>(int value, bool ignoreZero = false, bool humanize = true)
where TEnum : struct, IConvertible /// <summary>
{ /// Gets the flag names.
return Retrieve<TEnum>() /// </summary>
.When(() => ignoreZero, q => q.Where(f => (int) f.Item2 != 0)) /// <typeparam name="TEnum">The type of the enum.</typeparam>
.Where(x => ((int) x.Item2 & value) == (int) x.Item2) /// <param name="value">The value.</param>
.Select(x => humanize ? x.Item1.Humanize() : x.Item1); /// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
} /// <param name="humanize">if set to <c>true</c> [humanize].</param>
/// <returns>
/// <summary> /// A list of flag names.
/// Gets the flag names. /// </returns>
/// </summary> public static IEnumerable<String> GetFlagNames<TEnum>(Byte value, Boolean ignoreZero = false, Boolean humanize = true)
/// <typeparam name="TEnum">The type of the enum.</typeparam> where TEnum : struct, IConvertible => Retrieve<TEnum>()
/// <param name="value">The value.</param> .When(() => ignoreZero, q => q.Where(f => (Byte)f.Item2 != 0))
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param> .Where(x => ((Byte)x.Item2 & value) == (Byte)x.Item2)
/// <param name="humanize">if set to <c>true</c> [humanize].</param> .Select(x => humanize ? x.Item1.Humanize() : x.Item1);
/// <returns>
/// A list of flag names. /// <summary>
/// </returns> /// Gets the cached items with the enum item index.
public static IEnumerable<string> GetFlagNames<TEnum>(long value, bool ignoreZero = false, bool humanize = true) /// </summary>
where TEnum : struct, IConvertible /// <typeparam name="T">The type of enumeration.</typeparam>
{ /// <param name="humanize">if set to <c>true</c> [humanize].</param>
return Retrieve<TEnum>() /// <returns>
.When(() => ignoreZero, q => q.Where(f => (long) f.Item2 != 0)) /// A collection of Type/Tuple pairs that represents items with the enum item value.
.Where(x => ((long) x.Item2 & value) == (long) x.Item2) /// </returns>
.Select(x => humanize ? x.Item1.Humanize() : x.Item1); public static IEnumerable<Tuple<Int32, String>> GetItemsWithIndex<T>(Boolean humanize = true)
} where T : struct, IConvertible {
Int32 i = 0;
/// <summary>
/// Gets the flag names. return Retrieve<T>()
/// </summary> .Select(x => Tuple.Create(i++, humanize ? x.Item1.Humanize() : x.Item1));
/// <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));
}
}
} }

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

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/>
public Type SourceType { get; } List<PropertyInfo> sourceMembers = GetSourceMembers(sourceProperty);
/// <inheritdoc/> if(sourceMembers.Any() == false) {
public Type DestinationType { get; } throw new ArgumentException("Invalid source expression", nameof(sourceProperty));
}
/// <summary>
/// Maps the property. // reverse order
/// </summary> sourceMembers.Reverse();
/// <typeparam name="TDestinationProperty">The type of the destination property.</typeparam> this.Map[propertyDestinationInfo] = sourceMembers;
/// <typeparam name="TSourceProperty">The type of the source property.</typeparam>
/// <param name="destinationProperty">The destination property.</param> return this;
/// <param name="sourceProperty">The source property.</param> }
/// <returns>
/// An object map representation of type of the destination property /// <summary>
/// and type of the source property. /// Removes the map property.
/// </returns> /// </summary>
public ObjectMap<TSource, TDestination> MapProperty /// <typeparam name="TDestinationProperty">The type of the destination property.</typeparam>
<TDestinationProperty, TSourceProperty>( /// <param name="destinationProperty">The destination property.</param>
Expression<Func<TDestination, TDestinationProperty>> destinationProperty, /// <returns>
Expression<Func<TSource, TSourceProperty>> sourceProperty) /// An object map representation of type of the destination property
{ /// and type of the source property.
var propertyDestinationInfo = (destinationProperty.Body as MemberExpression)?.Member as PropertyInfo; /// </returns>
/// <exception cref="System.Exception">Invalid destination expression.</exception>
if (propertyDestinationInfo == null) public ObjectMap<TSource, TDestination> RemoveMapProperty<TDestinationProperty>(
{ Expression<Func<TDestination, TDestinationProperty>> destinationProperty) {
throw new ArgumentException("Invalid destination expression", nameof(destinationProperty)); PropertyInfo propertyDestinationInfo = (destinationProperty.Body as MemberExpression)?.Member as PropertyInfo;
}
if(propertyDestinationInfo == null) {
var sourceMembers = GetSourceMembers(sourceProperty); throw new ArgumentException("Invalid destination expression", nameof(destinationProperty));
}
if (sourceMembers.Any() == false)
{ if(this.Map.ContainsKey(propertyDestinationInfo)) {
throw new ArgumentException("Invalid source expression", nameof(sourceProperty)); _ = this.Map.Remove(propertyDestinationInfo);
} }
// reverse order return this;
sourceMembers.Reverse(); }
Map[propertyDestinationInfo] = sourceMembers;
private static List<PropertyInfo> GetSourceMembers<TSourceProperty>(Expression<Func<TSource, TSourceProperty>> sourceProperty) {
return this; List<PropertyInfo> sourceMembers = new List<PropertyInfo>();
} MemberExpression initialExpression = sourceProperty.Body as MemberExpression;
/// <summary> while(true) {
/// Removes the map property. PropertyInfo propertySourceInfo = initialExpression?.Member as PropertyInfo;
/// </summary>
/// <typeparam name="TDestinationProperty">The type of the destination property.</typeparam> if(propertySourceInfo == null) {
/// <param name="destinationProperty">The destination property.</param> break;
/// <returns> }
/// An object map representation of type of the destination property
/// and type of the source property. sourceMembers.Add(propertySourceInfo);
/// </returns> initialExpression = initialExpression.Expression as MemberExpression;
/// <exception cref="System.Exception">Invalid destination expression.</exception> }
public ObjectMap<TSource, TDestination> RemoveMapProperty<TDestinationProperty>(
Expression<Func<TDestination, TDestinationProperty>> destinationProperty) return sourceMembers;
{ }
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;
}
}
} }

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. if(target == null) {
/// <code> throw new ArgumentNullException(nameof(target));
/// using Unosquare.Swan }
///
/// class Example return Copy(
/// {
/// 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, 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> /// <summary>
/// Creates the map. /// Creates the map.
/// </summary> /// </summary>
/// <typeparam name="TSource">The type of the source.</typeparam> /// <typeparam name="TSource">The type of the source.</typeparam>
/// <typeparam name="TDestination">The type of the destination.</typeparam> /// <typeparam name="TDestination">The type of the destination.</typeparam>
/// <returns> /// <returns>
/// An object map representation of type of the destination property /// An object map representation of type of the destination property
/// and type of the source property. /// and type of the source property.
/// </returns> /// </returns>
/// <exception cref="System.InvalidOperationException"> /// <exception cref="System.InvalidOperationException">
/// You can't create an existing map /// You can't create an existing map
/// or /// or
/// Types doesn't match. /// Types doesn't match.
/// </exception> /// </exception>
public ObjectMap<TSource, TDestination> CreateMap<TSource, TDestination>() public ObjectMap<TSource, TDestination> CreateMap<TSource, TDestination>() {
{ if(this._maps.Any(x => x.SourceType == typeof(TSource) && x.DestinationType == typeof(TDestination))) {
if (_maps.Any(x => x.SourceType == typeof(TSource) && x.DestinationType == typeof(TDestination))) throw new InvalidOperationException("You can't create an existing map");
{ }
throw new InvalidOperationException("You can't create an existing map");
} IEnumerable<PropertyInfo> sourceType = Runtime.PropertyTypeCache.RetrieveAllProperties<TSource>(true);
IEnumerable<PropertyInfo> destinationType = Runtime.PropertyTypeCache.RetrieveAllProperties<TDestination>(true);
var sourceType = Runtime.PropertyTypeCache.RetrieveAllProperties<TSource>(true);
var destinationType = Runtime.PropertyTypeCache.RetrieveAllProperties<TDestination>(true); PropertyInfo[] intersect = sourceType.Intersect(destinationType, new PropertyInfoComparer()).ToArray();
var intersect = sourceType.Intersect(destinationType, new PropertyInfoComparer()).ToArray(); if(intersect.Any() == false) {
throw new InvalidOperationException("Types doesn't match");
if (intersect.Any() == false) }
{
throw new InvalidOperationException("Types doesn't match"); ObjectMap<TSource, TDestination> map = new ObjectMap<TSource, TDestination>(intersect);
}
this._maps.Add(map);
var map = new ObjectMap<TSource, TDestination>(intersect);
return map;
_maps.Add(map); }
return map; /// <summary>
} /// Maps the specified source.
/// </summary>
/// <summary> /// <typeparam name="TDestination">The type of the destination.</typeparam>
/// Maps the specified source. /// <param name="source">The source.</param>
/// </summary> /// <param name="autoResolve">if set to <c>true</c> [automatic resolve].</param>
/// <typeparam name="TDestination">The type of the destination.</typeparam> /// <returns>
/// <param name="source">The source.</param> /// A new instance of the map.
/// <param name="autoResolve">if set to <c>true</c> [automatic resolve].</param> /// </returns>
/// <returns> /// <exception cref="ArgumentNullException">source.</exception>
/// A new instance of the map. /// <exception cref="InvalidOperationException">You can't map from type {source.GetType().Name} to {typeof(TDestination).Name}.</exception>
/// </returns> public TDestination Map<TDestination>(Object source, Boolean autoResolve = true) {
/// <exception cref="ArgumentNullException">source.</exception> if(source == null) {
/// <exception cref="InvalidOperationException">You can't map from type {source.GetType().Name} to {typeof(TDestination).Name}.</exception> throw new ArgumentNullException(nameof(source));
public TDestination Map<TDestination>(object source, bool autoResolve = true) }
{
if (source == null) TDestination destination = Activator.CreateInstance<TDestination>();
{ IObjectMap map = this._maps
throw new ArgumentNullException(nameof(source)); .FirstOrDefault(x => x.SourceType == source.GetType() && x.DestinationType == typeof(TDestination));
}
if(map != null) {
var destination = Activator.CreateInstance<TDestination>(); foreach(KeyValuePair<PropertyInfo, List<PropertyInfo>> property in map.Map) {
var map = _maps Object finalSource = property.Value.Aggregate(source,
.FirstOrDefault(x => x.SourceType == source.GetType() && x.DestinationType == typeof(TDestination)); (current, sourceProperty) => sourceProperty.GetValue(current));
if (map != null) property.Key.SetValue(destination, finalSource);
{ }
foreach (var property in map.Map) } else {
{ if(!autoResolve) {
var finalSource = property.Value.Aggregate(source, throw new InvalidOperationException(
(current, sourceProperty) => sourceProperty.GetValue(current)); $"You can't map from type {source.GetType().Name} to {typeof(TDestination).Name}");
}
property.Key.SetValue(destination, finalSource);
} // Missing mapping, try to use default behavior
} _ = Copy(source, destination);
else }
{
if (!autoResolve) return destination;
{ }
throw new InvalidOperationException(
$"You can't map from type {source.GetType().Name} to {typeof(TDestination).Name}"); private static Int32 Copy(
} Object target,
IEnumerable<String> propertiesToCopy,
// Missing mapping, try to use default behavior IEnumerable<String> ignoreProperties,
Copy(source, destination); Dictionary<String, TypeValuePair> sourceProperties) {
} // Filter properties
IEnumerable<String> requiredProperties = propertiesToCopy?
return destination; .Where(p => !String.IsNullOrWhiteSpace(p))
} .Select(p => p.ToLowerInvariant());
private static int Copy( IEnumerable<String> ignoredProperties = ignoreProperties?
object target, .Where(p => !String.IsNullOrWhiteSpace(p))
IEnumerable<string> propertiesToCopy, .Select(p => p.ToLowerInvariant());
IEnumerable<string> ignoreProperties,
Dictionary<string, TypeValuePair> sourceProperties) IEnumerable<PropertyInfo> properties = Runtime.PropertyTypeCache
{ .RetrieveFilteredProperties(target.GetType(), true, x => x.CanWrite);
// Filter properties
var requiredProperties = propertiesToCopy? return properties
.Where(p => !string.IsNullOrWhiteSpace(p)) .Select(x => x.Name)
.Select(p => p.ToLowerInvariant()); .Distinct()
.ToDictionary(x => x.ToLowerInvariant(), x => properties.First(y => y.Name == x))
var ignoredProperties = ignoreProperties? .Where(x => sourceProperties.Keys.Contains(x.Key))
.Where(p => !string.IsNullOrWhiteSpace(p)) .When(() => requiredProperties != null, q => q.Where(y => requiredProperties.Contains(y.Key)))
.Select(p => p.ToLowerInvariant()); .When(() => ignoredProperties != null, q => q.Where(y => !ignoredProperties.Contains(y.Key)))
.ToDictionary(x => x.Value, x => sourceProperties[x.Key])
var properties = Runtime.PropertyTypeCache .Sum(x => TrySetValue(x, target) ? 1 : 0);
.RetrieveFilteredProperties(target.GetType(), true, x => x.CanWrite); }
return properties private static Boolean TrySetValue(KeyValuePair<PropertyInfo, TypeValuePair> property, Object target) {
.Select(x => x.Name) try {
.Distinct() SetValue(property, target);
.ToDictionary(x => x.ToLowerInvariant(), x => properties.First(y => y.Name == x)) return true;
.Where(x => sourceProperties.Keys.Contains(x.Key)) } catch {
.When(() => requiredProperties != null, q => q.Where(y => requiredProperties.Contains(y.Key))) // swallow
.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); return false;
} }
private static bool TrySetValue(KeyValuePair<PropertyInfo, TypeValuePair> property, object target) private static void SetValue(KeyValuePair<PropertyInfo, TypeValuePair> property, Object target) {
{ if(property.Value.Type.GetTypeInfo().IsEnum) {
try property.Key.SetValue(target,
{ Enum.ToObject(property.Key.PropertyType, property.Value.Value));
SetValue(property, target);
return true; return;
} }
catch
{ if(!property.Value.Type.IsValueType() && property.Key.PropertyType == property.Value.Type) {
// swallow property.Key.SetValue(target, GetValue(property.Value.Value, property.Key.PropertyType));
}
return;
return false; }
}
if(property.Key.PropertyType == typeof(Boolean)) {
private static void SetValue(KeyValuePair<PropertyInfo, TypeValuePair> property, object target) property.Key.SetValue(target,
{ Convert.ToBoolean(property.Value.Value));
if (property.Value.Type.GetTypeInfo().IsEnum)
{ return;
property.Key.SetValue(target, }
Enum.ToObject(property.Key.PropertyType, property.Value.Value));
_ = property.Key.TrySetBasicType(property.Value.Value, target);
return; }
}
private static Object GetValue(Object source, Type targetType) {
if (!property.Value.Type.IsValueType() && property.Key.PropertyType == property.Value.Type) if(source == null) {
{ return null;
property.Key.SetValue(target, GetValue(property.Value.Value, property.Key.PropertyType)); }
return; Object target = null;
}
source.CreateTarget(targetType, false, ref target);
if (property.Key.PropertyType == typeof(bool))
{ switch(source) {
property.Key.SetValue(target, case String _:
Convert.ToBoolean(property.Value.Value)); target = source;
break;
return; case IList sourceList when target is Array targetArray:
} for(Int32 i = 0; i < sourceList.Count; i++) {
try {
property.Key.TrySetBasicType(property.Value.Value, target); targetArray.SetValue(
} sourceList[i].GetType().IsValueType()
? sourceList[i]
private static object GetValue(object source, Type targetType) : sourceList[i].CopyPropertiesToNew<Object>(), i);
{ } catch {
if (source == null) // ignored
return null; }
}
object target = null;
break;
source.CreateTarget(targetType, false, ref target); case IList sourceList when target is IList targetList:
MethodInfo addMethod = targetType.GetMethods()
switch (source) .FirstOrDefault(
{ m => m.Name.Equals(Formatters.Json.AddMethodName) && m.IsPublic &&
case string _: m.GetParameters().Length == 1);
target = source;
break; if(addMethod == null) {
case IList sourceList when target is Array targetArray: return target;
for (var i = 0; i < sourceList.Count; i++) }
{
try foreach(Object item in sourceList) {
{ try {
targetArray.SetValue( _ = targetList.Add(item.GetType().IsValueType()
sourceList[i].GetType().IsValueType() ? item
? sourceList[i] : item.CopyPropertiesToNew<Object>());
: sourceList[i].CopyPropertiesToNew<object>(), i); } catch {
} // ignored
catch }
{ }
// ignored
} break;
} default:
_ = source.CopyPropertiesTo(target);
break; break;
case IList sourceList when target is IList targetList: }
var addMethod = targetType.GetMethods()
.FirstOrDefault( return target;
m => m.Name.Equals(Formatters.Json.AddMethodName) && m.IsPublic && }
m.GetParameters().Length == 1);
private static Dictionary<String, TypeValuePair> GetSourceMap(Object source) {
if (addMethod == null) return target; // select distinct properties because they can be duplicated by inheritance
PropertyInfo[] sourceProperties = Runtime.PropertyTypeCache
foreach (var item in sourceList) .RetrieveFilteredProperties(source.GetType(), true, x => x.CanRead)
{ .ToArray();
try
{ return sourceProperties
targetList.Add(item.GetType().IsValueType() .Select(x => x.Name)
? item .Distinct()
: item.CopyPropertiesToNew<object>()); .ToDictionary(
} x => x.ToLowerInvariant(),
catch x => new TypeValuePair(sourceProperties.First(y => y.Name == x).PropertyType,
{ sourceProperties.First(y => y.Name == x).GetValue(source)));
// ignored }
}
} internal class TypeValuePair {
public TypeValuePair(Type type, Object value) {
break; this.Type = type;
default: this.Value = value;
source.CopyPropertiesTo(target); }
break;
} public Type Type {
get;
return target; }
}
public Object Value {
private static Dictionary<string, TypeValuePair> GetSourceMap(object source) get;
{ }
// select distinct properties because they can be duplicated by inheritance }
var sourceProperties = Runtime.PropertyTypeCache
.RetrieveFilteredProperties(source.GetType(), true, x => x.CanRead) internal class PropertyInfoComparer : IEqualityComparer<PropertyInfo> {
.ToArray(); public Boolean Equals(PropertyInfo x, PropertyInfo y)
=> x != null && y != null && x.Name == y.Name && x.PropertyType == y.PropertyType;
return sourceProperties
.Select(x => x.Name) public Int32 GetHashCode(PropertyInfo obj)
.Distinct() => obj.Name.GetHashCode() + obj.PropertyType.Name.GetHashCode();
.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();
}
}
} }

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() return errorList;
/// { }
/// // 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> /// <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>
/// <c>true</c> if there are no errors; otherwise, <c>false</c>. /// <summary>
/// </summary> /// Adds a validator to a specific class.
public bool IsValid => !Errors.Any(); /// </summary>
/// <typeparam name="T">The type of the object.</typeparam>
/// <summary> /// <param name="predicate">The predicate that will be evaluated.</param>
/// Adds an error with a specified property name. /// <param name="message">The message.</param>
/// </summary> /// <exception cref="ArgumentNullException">
/// <param name="propertyName">The property name.</param> /// predicate
/// <param name="errorMessage">The error message.</param> /// or
public void Add(string propertyName, string errorMessage) => /// message.
Errors.Add(new ValidationError {ErrorMessage = errorMessage, PropertyName = propertyName}); /// </exception>
public void AddValidator<T>(Predicate<T> predicate, String message)
/// <summary> where T : class {
/// Defines a validation error. if(predicate == null) {
/// </summary> throw new ArgumentNullException(nameof(predicate));
public class ValidationError }
{
/// <summary> if(String.IsNullOrEmpty(message)) {
/// The property name. throw new ArgumentNullException(message);
/// </summary> }
public string PropertyName { get; set; }
if(!this._predicates.TryGetValue(typeof(T), out List<Tuple<Delegate, String>> existing)) {
/// <summary> existing = new List<Tuple<Delegate, String>>();
/// The message error. this._predicates[typeof(T)] = existing;
/// </summary> }
public string ErrorMessage { get; set; }
} existing.Add(Tuple.Create((Delegate)predicate, message));
} }
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>
/// Enumerates the locking operations. /// <summary>
/// </summary> /// Defines methods for releasing locks.
private enum LockHolderType /// </summary>
{ private interface ISyncReleasable {
Read, /// <summary>
Write, /// Releases the writer lock.
} /// </summary>
void ReleaseWriterLock();
/// <summary>
/// Defines methods for releasing locks. /// <summary>
/// </summary> /// Releases the reader lock.
private interface ISyncReleasable /// </summary>
{ void ReleaseReaderLock();
/// <summary> }
/// Releases the writer lock.
/// </summary> #endregion
void ReleaseWriterLock();
#region Factory Methods
/// <summary>
/// Releases the reader lock. #if !NETSTANDARD1_3
/// </summary> /// <summary>
void ReleaseReaderLock(); /// Creates a reader-writer lock backed by a standard ReaderWriterLock.
} /// </summary>
/// <returns>The synchronized locker.</returns>
#endregion public static ISyncLocker Create() => new SyncLocker();
#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 #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
@ -55,143 +51,142 @@
/// <returns>The synchronized locker</returns> /// <returns>The synchronized locker</returns>
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 Boolean _isDisposed;
private bool _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;
{ this._operation = operation;
_parent = parent; }
_operation = operation;
} /// <inheritdoc />
public void Dispose() {
/// <inheritdoc /> if(this._isDisposed) {
public void Dispose() return;
{ }
if (_isDisposed) return;
_isDisposed = true; this._isDisposed = true;
if (_operation == LockHolderType.Read) if(this._operation == LockHolderType.Read) {
_parent.ReleaseReaderLock(); this._parent.ReleaseReaderLock();
else } else {
_parent.ReleaseWriterLock(); this._parent.ReleaseWriterLock();
} }
} }
}
#if !NETSTANDARD1_3
/// <summary> #if !NETSTANDARD1_3
/// The Sync Locker backed by a ReaderWriterLock. /// <summary>
/// </summary> /// The Sync Locker backed by a ReaderWriterLock.
/// <seealso cref="ISyncLocker" /> /// </summary>
/// <seealso cref="ISyncReleasable" /> /// <seealso cref="ISyncLocker" />
private sealed class SyncLocker : ISyncLocker, ISyncReleasable /// <seealso cref="ISyncReleasable" />
{ private sealed class SyncLocker : ISyncLocker, ISyncReleasable {
private bool _isDisposed; private Boolean _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);
{ return new SyncLockReleaser(this, LockHolderType.Write);
_locker?.AcquireWriterLock(Timeout.Infinite); }
return new SyncLockReleaser(this, LockHolderType.Write);
} /// <inheritdoc />
public void ReleaseWriterLock() => this._locker?.ReleaseWriterLock();
/// <inheritdoc />
public void ReleaseWriterLock() => _locker?.ReleaseWriterLock(); /// <inheritdoc />
public void ReleaseReaderLock() => this._locker?.ReleaseReaderLock();
/// <inheritdoc />
public void ReleaseReaderLock() => _locker?.ReleaseReaderLock(); /// <inheritdoc />
public void Dispose() {
/// <inheritdoc /> if(this._isDisposed) {
public void Dispose() return;
{ }
if (_isDisposed) return;
_isDisposed = true; this._isDisposed = true;
_locker?.ReleaseLock(); _ = this._locker?.ReleaseLock();
_locker = null; 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();
{ return new SyncLockReleaser(this, LockHolderType.Read);
_locker?.EnterReadLock(); }
return new SyncLockReleaser(this, LockHolderType.Read);
} /// <inheritdoc />
public IDisposable AcquireWriterLock() {
/// <inheritdoc /> this._locker?.EnterWriteLock();
public IDisposable AcquireWriterLock() return new SyncLockReleaser(this, LockHolderType.Write);
{ }
_locker?.EnterWriteLock();
return new SyncLockReleaser(this, LockHolderType.Write); /// <inheritdoc />
} public void ReleaseWriterLock() => this._locker?.ExitWriteLock();
/// <inheritdoc /> /// <inheritdoc />
public void ReleaseWriterLock() => _locker?.ExitWriteLock(); public void ReleaseReaderLock() => this._locker?.ExitReadLock();
/// <inheritdoc /> /// <inheritdoc />
public void ReleaseReaderLock() => _locker?.ExitReadLock(); public void Dispose() {
if(this._isDisposed) {
/// <inheritdoc /> return;
public void Dispose() }
{
if (_isDisposed) return; this._isDisposed = true;
_isDisposed = true; this._locker?.Dispose();
_locker?.Dispose(); this._locker = null;
_locker = null; }
} }
}
#endregion
#endregion }
}
} }

View File

@ -1,63 +1,55 @@
#if !NETSTANDARD1_3 #if !NETSTANDARD1_3
namespace Unosquare.Swan.Components
{
using System;
using System.Threading;
using Abstractions;
using System;
using System.Threading;
using Unosquare.Swan.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) {
{ this._delayLock.Wait();
while (!ct.IsCancellationRequested && DateTime.UtcNow < untilDate) }
_delayLock.Wait(); }
}
/// <summary>
/// <summary> /// Waits the specified wait time.
/// Waits the specified wait time. /// </summary>
/// </summary> /// <param name="waitTime">The wait time.</param>
/// <param name="waitTime">The wait time.</param> /// <param name="ct">The cancellation token.</param>
/// <param name="ct">The cancellation token.</param> public void Wait(TimeSpan waitTime, CancellationToken ct = default) =>
public void Wait(TimeSpan waitTime, CancellationToken ct = default) => this.WaitUntil(DateTime.UtcNow.Add(waitTime), ct);
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;
/// /// <summary>
/// public class Example /// Creates a Wait Event backed by a ManualResetEventSlim.
/// { /// </summary>
/// // create a WaitEvent using the slim version /// <param name="isCompleted">if initially set to completed. Generally true.</param>
/// private static readonly IWaitEvent waitEvent = WaitEventFactory.CreateSlim(false); /// <returns>The Wait Event.</returns>
/// public static IWaitEvent CreateSlim(Boolean isCompleted) => new WaitEventSlim(isCompleted);
/// public static void Main()
/// { /// <summary>
/// Task.Factory.StartNew(() => /// Creates a Wait Event backed by a ManualResetEventSlim.
/// { /// </summary>
/// DoWork(1); /// <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>
/// Task.Factory.StartNew(() => public static IWaitEvent Create(Boolean isCompleted, Boolean useSlim) => useSlim ? CreateSlim(isCompleted) : Create(isCompleted);
/// {
/// DoWork(2); #endregion
/// });
/// #region Backing Classes
/// // send first signal
/// waitEvent.Complete(); /// <summary>
/// waitEvent.Begin(); /// Defines a WaitEvent backed by a ManualResetEvent.
/// /// </summary>
/// Thread.Sleep(TimeSpan.FromSeconds(2)); private class WaitEvent : IWaitEvent {
/// private ManualResetEvent _event;
/// // send second signal
/// waitEvent.Complete(); /// <summary>
/// /// Initializes a new instance of the <see cref="WaitEvent"/> class.
/// Console.Readline(); /// </summary>
/// } /// <param name="isCompleted">if set to <c>true</c> [is completed].</param>
/// public WaitEvent(Boolean isCompleted) => this._event = new ManualResetEvent(isCompleted);
/// public static void DoWork(int taskNumber)
/// { /// <inheritdoc />
/// $"Data retrieved:{taskNumber}".WriteLine(); public Boolean IsDisposed {
/// waitEvent.Wait(); get; private set;
/// }
/// Thread.Sleep(TimeSpan.FromSeconds(2));
/// $"All finished up {taskNumber}".WriteLine(); /// <inheritdoc />
/// } public Boolean IsValid => this.IsDisposed || this._event == null
/// } ? false
/// </code> : this._event?.SafeWaitHandle?.IsClosed ?? true ? false : !(this._event?.SafeWaitHandle?.IsInvalid ?? true);
/// </example>
public static class WaitEventFactory /// <inheritdoc />
{ public Boolean IsCompleted => this.IsValid == false ? true : this._event?.WaitOne(0) ?? true;
#region Factory Methods
/// <inheritdoc />
/// <summary> public Boolean IsInProgress => !this.IsCompleted;
/// Creates a Wait Event backed by a standard ManualResetEvent.
/// </summary> /// <inheritdoc />
/// <param name="isCompleted">if initially set to completed. Generally true.</param> public void Begin() => this._event?.Reset();
/// <returns>The Wait Event.</returns>
public static IWaitEvent Create(bool isCompleted) => new WaitEvent(isCompleted); /// <inheritdoc />
public void Complete() => this._event?.Set();
/// <summary>
/// Creates a Wait Event backed by a ManualResetEventSlim. /// <inheritdoc />
/// </summary> void IDisposable.Dispose() {
/// <param name="isCompleted">if initially set to completed. Generally true.</param> if(this.IsDisposed) {
/// <returns>The Wait Event.</returns> return;
public static IWaitEvent CreateSlim(bool isCompleted) => new WaitEventSlim(isCompleted); }
/// <summary> this.IsDisposed = true;
/// Creates a Wait Event backed by a ManualResetEventSlim.
/// </summary> _ = this._event?.Set();
/// <param name="isCompleted">if initially set to completed. Generally true.</param> this._event?.Dispose();
/// <param name="useSlim">if set to <c>true</c> creates a slim version of the wait event.</param> this._event = null;
/// <returns>The Wait Event.</returns> }
public static IWaitEvent Create(bool isCompleted, bool useSlim) => useSlim ? CreateSlim(isCompleted) : Create(isCompleted);
/// <inheritdoc />
#endregion public void Wait() => this._event?.WaitOne();
#region Backing Classes /// <inheritdoc />
public Boolean Wait(TimeSpan timeout) => this._event?.WaitOne(timeout) ?? true;
/// <summary> }
/// Defines a WaitEvent backed by a ManualResetEvent.
/// </summary> /// <summary>
private class WaitEvent : IWaitEvent /// Defines a WaitEvent backed by a ManualResetEventSlim.
{ /// </summary>
private ManualResetEvent _event; private class WaitEventSlim : IWaitEvent {
private ManualResetEventSlim _event;
/// <summary>
/// Initializes a new instance of the <see cref="WaitEvent"/> class. /// <summary>
/// </summary> /// Initializes a new instance of the <see cref="WaitEventSlim"/> class.
/// <param name="isCompleted">if set to <c>true</c> [is completed].</param> /// </summary>
public WaitEvent(bool isCompleted) /// <param name="isCompleted">if set to <c>true</c> [is completed].</param>
{ public WaitEventSlim(Boolean isCompleted) => this._event = new ManualResetEventSlim(isCompleted);
_event = new ManualResetEvent(isCompleted);
} /// <inheritdoc />
public Boolean IsDisposed {
/// <inheritdoc /> get; private set;
public bool IsDisposed { get; private set; } }
/// <inheritdoc /> /// <inheritdoc />
public bool IsValid public Boolean IsValid => this.IsDisposed || this._event?.WaitHandle?.SafeWaitHandle == null
{ ? false
get : !this._event.WaitHandle.SafeWaitHandle.IsClosed && !this._event.WaitHandle.SafeWaitHandle.IsInvalid;
{
if (IsDisposed || _event == null) /// <inheritdoc />
return false; public Boolean IsCompleted => this.IsValid == false || this._event.IsSet;
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 void Complete() => this._event?.Set();
public bool IsCompleted
{ /// <inheritdoc />
get void IDisposable.Dispose() {
{ if(this.IsDisposed) {
if (IsValid == false) return;
return true; }
return _event?.WaitOne(0) ?? true; this.IsDisposed = true;
}
} this._event?.Set();
this._event?.Dispose();
/// <inheritdoc /> this._event = null;
public bool IsInProgress => !IsCompleted; }
/// <inheritdoc /> /// <inheritdoc />
public void Begin() => _event?.Reset(); public void Wait() => this._event?.Wait();
/// <inheritdoc /> /// <inheritdoc />
public void Complete() => _event?.Set(); public Boolean Wait(TimeSpan timeout) => this._event?.Wait(timeout) ?? true;
}
/// <inheritdoc />
void IDisposable.Dispose() #endregion
{ }
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 #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> /// <summary>
/// Represents a struct of DateTimeSpan to compare dates and get in /// Initializes a new instance of the <see cref="DateTimeSpan"/> struct.
/// separate fields the amount of time between those dates.
///
/// Based on https://stackoverflow.com/a/9216404/1096693.
/// </summary> /// </summary>
public struct DateTimeSpan /// <param name="years">The years.</param>
{ /// <param name="months">The months.</param>
/// <summary> /// <param name="days">The days.</param>
/// Initializes a new instance of the <see cref="DateTimeSpan"/> struct. /// <param name="hours">The hours.</param>
/// </summary> /// <param name="minutes">The minutes.</param>
/// <param name="years">The years.</param> /// <param name="seconds">The seconds.</param>
/// <param name="months">The months.</param> /// <param name="milliseconds">The milliseconds.</param>
/// <param name="days">The days.</param> public DateTimeSpan(Int32 years, Int32 months, Int32 days, Int32 hours, Int32 minutes, Int32 seconds, Int32 milliseconds) {
/// <param name="hours">The hours.</param> this.Years = years;
/// <param name="minutes">The minutes.</param> this.Months = months;
/// <param name="seconds">The seconds.</param> this.Days = days;
/// <param name="milliseconds">The milliseconds.</param> this.Hours = hours;
public DateTimeSpan(int years, int months, int days, int hours, int minutes, int seconds, int milliseconds) this.Minutes = minutes;
{ this.Seconds = seconds;
Years = years; this.Milliseconds = milliseconds;
Months = months; }
Days = days;
Hours = hours; /// <summary>
Minutes = minutes; /// Gets the years.
Seconds = seconds; /// </summary>
Milliseconds = milliseconds; /// <value>
} /// The years.
/// </value>
/// <summary> public Int32 Years {
/// Gets the years. get;
/// </summary> }
/// <value>
/// The years. /// <summary>
/// </value> /// Gets the months.
public int Years { get; } /// </summary>
/// <value>
/// <summary> /// The months.
/// Gets the months. /// </value>
/// </summary> public Int32 Months {
/// <value> get;
/// The months. }
/// </value>
public int Months { get; } /// <summary>
/// Gets the days.
/// <summary> /// </summary>
/// Gets the days. /// <value>
/// </summary> /// The days.
/// <value> /// </value>
/// The days. public Int32 Days {
/// </value> get;
public int Days { get; } }
/// <summary> /// <summary>
/// Gets the hours. /// Gets the hours.
/// </summary> /// </summary>
/// <value> /// <value>
/// The hours. /// The hours.
/// </value> /// </value>
public int Hours { get; } public Int32 Hours {
get;
/// <summary> }
/// Gets the minutes.
/// </summary> /// <summary>
/// <value> /// Gets the minutes.
/// The minutes. /// </summary>
/// </value> /// <value>
public int Minutes { get; } /// The minutes.
/// </value>
/// <summary> public Int32 Minutes {
/// Gets the seconds. get;
/// </summary> }
/// <value>
/// The seconds. /// <summary>
/// </value> /// Gets the seconds.
public int Seconds { get; } /// </summary>
/// <value>
/// <summary> /// The seconds.
/// Gets the milliseconds. /// </value>
/// </summary> public Int32 Seconds {
/// <value> get;
/// The milliseconds. }
/// </value>
public int Milliseconds { get; } /// <summary>
/// Gets the milliseconds.
internal static DateTimeSpan CompareDates(DateTime date1, DateTime date2) /// </summary>
{ /// <value>
if (date2 < date1) /// The milliseconds.
{ /// </value>
var sub = date1; public Int32 Milliseconds {
date1 = date2; get;
date2 = sub; }
}
internal static DateTimeSpan CompareDates(DateTime date1, DateTime date2) {
var current = date1; if(date2 < date1) {
var years = 0; DateTime sub = date1;
var months = 0; date1 = date2;
var days = 0; date2 = sub;
}
var phase = Phase.Years;
var span = new DateTimeSpan(); DateTime current = date1;
var officialDay = current.Day; Int32 years = 0;
Int32 months = 0;
while (phase != Phase.Done) Int32 days = 0;
{
switch (phase) Phase phase = Phase.Years;
{ DateTimeSpan span = new DateTimeSpan();
case Phase.Years: Int32 officialDay = current.Day;
if (current.AddYears(years + 1) > date2)
{ while(phase != Phase.Done) {
phase = Phase.Months; switch(phase) {
current = current.AddYears(years); case Phase.Years:
} if(current.AddYears(years + 1) > date2) {
else phase = Phase.Months;
{ current = current.AddYears(years);
years++; } else {
} years++;
}
break;
case Phase.Months: break;
if (current.AddMonths(months + 1) > date2) case Phase.Months:
{ if(current.AddMonths(months + 1) > date2) {
phase = Phase.Days; phase = Phase.Days;
current = current.AddMonths(months); current = current.AddMonths(months);
if (current.Day < officialDay && if(current.Day < officialDay &&
officialDay <= DateTime.DaysInMonth(current.Year, current.Month)) officialDay <= DateTime.DaysInMonth(current.Year, current.Month)) {
current = current.AddDays(officialDay - current.Day); current = current.AddDays(officialDay - current.Day);
} }
else } else {
{ months++;
months++; }
}
break;
break; case Phase.Days:
case Phase.Days: if(current.AddDays(days + 1) > date2) {
if (current.AddDays(days + 1) > date2) current = current.AddDays(days);
{ TimeSpan timespan = date2 - current;
current = current.AddDays(days); span = new DateTimeSpan(
var timespan = date2 - current; years,
span = new DateTimeSpan( months,
years, days,
months, timespan.Hours,
days, timespan.Minutes,
timespan.Hours, timespan.Seconds,
timespan.Minutes, timespan.Milliseconds);
timespan.Seconds, phase = Phase.Done;
timespan.Milliseconds); } else {
phase = Phase.Done; days++;
} }
else
{ break;
days++; }
} }
break; return span;
} }
}
private enum Phase {
return span; Years,
} Months,
Days,
private enum Phase Done,
{ }
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)
/// <summary> .Select(kvp => kvp.Key).ToArray());
/// Gets all numeric types without their nullable counterparts.
/// Note that Booleans and Guids are not considered numeric types. /// <summary>
/// </summary> /// Gets all numeric types without their nullable counterparts.
/// <value> /// Note that Booleans and Guids are not considered numeric types.
/// All numeric value types. /// </summary>
/// </value> /// <value>
public static List<Type> AllNumericValueTypes { get; } = new List<Type>( /// All numeric value types.
BasicTypesInfo /// </value>
.Where(kvp => kvp.Value.IsNumeric && kvp.Value.IsNullableValueType == false) public static List<Type> AllNumericValueTypes {
.Select(kvp => kvp.Key).ToArray()); get;
} = new List<Type>(
/// <summary> BasicTypesInfo
/// Contains all basic value types. i.e. excludes string and nullables. .Where(kvp => kvp.Value.IsNumeric && kvp.Value.IsNullableValueType == false)
/// </summary> .Select(kvp => kvp.Key).ToArray());
/// <value>
/// All basic value types. /// <summary>
/// </value> /// Contains all basic value types. i.e. excludes string and nullables.
public static List<Type> AllBasicValueTypes { get; } = new List<Type>( /// </summary>
BasicTypesInfo /// <value>
.Where(kvp => kvp.Value.IsValueType) /// All basic value types.
.Select(kvp => kvp.Key).ToArray()); /// </value>
public static List<Type> AllBasicValueTypes {
/// <summary> get;
/// Contains all basic value types including the string type. i.e. excludes nullables. } = new List<Type>(
/// </summary> BasicTypesInfo
/// <value> .Where(kvp => kvp.Value.IsValueType)
/// All basic value and string types. .Select(kvp => kvp.Key).ToArray());
/// </value>
public static List<Type> AllBasicValueAndStringTypes { get; } = new List<Type>( /// <summary>
BasicTypesInfo /// Contains all basic value types including the string type. i.e. excludes nullables.
.Where(kvp => kvp.Value.IsValueType || kvp.Key == typeof(string)) /// </summary>
.Select(kvp => kvp.Key).ToArray()); /// <value>
/// All basic value and string types.
/// <summary> /// </value>
/// Gets all nullable value types. i.e. excludes string and all basic value types. public static List<Type> AllBasicValueAndStringTypes {
/// </summary> get;
/// <value> } = new List<Type>(
/// All basic nullable value types. BasicTypesInfo
/// </value> .Where(kvp => kvp.Value.IsValueType || kvp.Key == typeof(String))
public static List<Type> AllBasicNullableValueTypes { get; } = new List<Type>( .Select(kvp => kvp.Key).ToArray());
BasicTypesInfo
.Where(kvp => kvp.Value.IsNullableValueType) /// <summary>
.Select(kvp => kvp.Key).ToArray()); /// Gets all nullable value types. i.e. excludes string and all basic value types.
} /// </summary>
/// <value>
/// All basic nullable value types.
/// </value>
public static List<Type> AllBasicNullableValueTypes {
get;
} = new List<Type>(
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> /// <summary>
/// Contains useful constants and definitions. /// The MS Windows codepage 1252 encoding used in some legacy scenarios
/// such as default CSV text encoding from Excel.
/// </summary> /// </summary>
public static partial class Definitions public static readonly Encoding Windows1252Encoding;
{
/// <summary> /// <summary>
/// The MS Windows codepage 1252 encoding used in some legacy scenarios /// The encoding associated with the default ANSI code page in the operating
/// such as default CSV text encoding from Excel. /// system's regional and language settings.
/// </summary> /// </summary>
public static readonly Encoding Windows1252Encoding; public static readonly Encoding CurrentAnsiEncoding;
/// <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> /// <summary>
/// In big endian, you store the most significant byte in the smallest address. /// macOS (OSX)
/// </summary> /// </summary>
Big, Osx,
}
/// <summary>
/// In little endian, you store the least significant byte in the smallest address. /// <summary>
/// </summary> /// Enumerates the different Application Worker States.
Little, /// </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) /// <summary>
: base( /// Gets the type of the message.
data.Sequence, /// It can be a combination as the enumeration is a set of bitwise flags.
data.MessageType, /// </summary>
data.UtcDate, /// <value>
data.Source, /// The type of the message.
data.Message, /// </value>
data.ExtendedData, public LogMessageType MessageType {
data.CallerMemberName, get;
data.CallerFilePath, }
data.CallerLineNumber)
{ /// <summary>
CancelOutput = false; /// Gets the UTC date at which the event at which the message was logged.
} /// </summary>
/// <value>
/// <summary> /// The UTC date.
/// Gets or sets a value indicating whether the displaying of the /// </value>
/// logging message should be canceled. public DateTime UtcDate {
/// </summary> get;
/// <value> }
/// <c>true</c> if [cancel output]; otherwise, <c>false</c>.
/// </value> /// <summary>
public bool CancelOutput { get; set; } /// 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,230 +1,231 @@
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>
/// <summary> /// Provides various extension methods for dates.
/// Provides various extension methods for dates. /// </summary>
/// </summary> public static class DateExtensions {
public static class DateExtensions private static readonly Dictionary<String, Int32> DateRanges = new Dictionary<String, Int32>()
{ {
private static readonly Dictionary<string, int> DateRanges = new Dictionary<string, int>()
{
{ "minute", 59}, { "minute", 59},
{ "hour", 23}, { "hour", 23},
{ "dayOfMonth", 31}, { "dayOfMonth", 31},
{ "month", 12}, { "month", 12},
{ "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('-'); String[] dateParts = dateTimeParts[0].Split('-');
if (dateParts.Length != 3) throw new Exception(); if(dateParts.Length != 3) {
throw new Exception();
var year = int.Parse(dateParts[0]); }
var month = int.Parse(dateParts[1]);
var day = int.Parse(dateParts[2]); Int32 year = Int32.Parse(dateParts[0]);
Int32 month = Int32.Parse(dateParts[1]);
if (dateTimeParts.Length > 1) Int32 day = Int32.Parse(dateParts[2]);
{
var timeParts = dateTimeParts[1].Split(':'); if(dateTimeParts.Length > 1) {
if (timeParts.Length != 3) throw new Exception(); String[] timeParts = dateTimeParts[1].Split(':');
if(timeParts.Length != 3) {
hour = int.Parse(timeParts[0]); throw new Exception();
minute = int.Parse(timeParts[1]); }
second = int.Parse(timeParts[2]);
} hour = Int32.Parse(timeParts[0]);
minute = Int32.Parse(timeParts[1]);
return new DateTime(year, month, day, hour, minute, second); second = Int32.Parse(timeParts[2]);
} }
catch (Exception)
{ return new DateTime(year, month, day, hour, minute, second);
throw new ArgumentException("Unable to parse sortable date and time.", nameof(sortableDate)); } catch(Exception) {
} throw new ArgumentException("Unable to parse sortable date and time.", nameof(sortableDate));
} }
}
/// <summary>
/// Creates a date's range. /// <summary>
/// </summary> /// Creates a date's range.
/// <param name="startDate">The start date.</param> /// </summary>
/// <param name="endDate">The end date.</param> /// <param name="startDate">The start date.</param>
/// <returns> /// <param name="endDate">The end date.</param>
/// A sequence of integral numbers within a specified date's range. /// <returns>
/// </returns> /// A sequence of integral numbers within a specified date's range.
public static IEnumerable<DateTime> DateRange(this DateTime startDate, DateTime endDate) /// </returns>
=> Enumerable.Range(0, (endDate - startDate).Days + 1).Select(d => startDate.AddDays(d)); 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>
/// </summary> /// Rounds up a date to match a timespan.
/// <param name="date">The datetime.</param> /// </summary>
/// <param name="timeSpan">The timespan to match.</param> /// <param name="date">The datetime.</param>
/// <returns> /// <param name="timeSpan">The timespan to match.</param>
/// A new instance of the DateTime structure to the specified datetime and timespan ticks. /// <returns>
/// </returns> /// A new instance of the DateTime structure to the specified datetime and timespan ticks.
public static DateTime RoundUp(this DateTime date, TimeSpan timeSpan) /// </returns>
=> new DateTime(((date.Ticks + timeSpan.Ticks - 1) / timeSpan.Ticks) * timeSpan.Ticks); 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>
/// </summary> /// Get this datetime as a Unix epoch timestamp (seconds since Jan 1, 1970, midnight UTC).
/// <param name="date">The date to convert.</param> /// </summary>
/// <returns>Seconds since Unix epoch.</returns> /// <param name="date">The date to convert.</param>
public static long ToUnixEpochDate(this DateTime date) /// <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 Boolean? GetElementParts(Int32? status, Int32 value) => status.HasValue ? status.Value == value : (Boolean?)null;
private static bool? GetElementParts(string parts, string type, int value) private static Boolean? GetElementParts(String parts, String type, Int32 value) {
{ if(String.IsNullOrWhiteSpace(parts) || parts == "*") {
if (string.IsNullOrWhiteSpace(parts) || parts == "*") return null; return null;
}
if (parts.Contains(","))
{ if(parts.Contains(",")) {
return parts.Split(',').Select(int.Parse).Contains(value); return parts.Split(',').Select(Int32.Parse).Contains(value);
} }
var stop = DateRanges[type]; Int32 stop = DateRanges[type];
if (parts.Contains("/")) if(parts.Contains("/")) {
{ Int32 multiple = Int32.Parse(parts.Split('/').Last());
var multiple = int.Parse(parts.Split('/').Last()); Int32 start = type == "dayOfMonth" || type == "month" ? 1 : 0;
var start = type == "dayOfMonth" || type == "month" ? 1 : 0;
for(Int32 i = start; i <= stop; i += multiple) {
for (var i = start; i <= stop; i += multiple) if(i == value) {
if (i == value) return true; return true;
}
return false; }
}
return false;
if (parts.Contains("-")) }
{
var range = parts.Split('-'); if(parts.Contains("-")) {
var start = int.Parse(range.First()); String[] range = parts.Split('-');
stop = Math.Max(stop, int.Parse(range.Last())); Int32 start = Int32.Parse(range.First());
stop = Math.Max(stop, Int32.Parse(range.Last()));
if ((type == "dayOfMonth" || type == "month") && start == 0)
start = 1; if((type == "dayOfMonth" || type == "month") && start == 0) {
start = 1;
for (var i = start; i <= stop; i++) }
if (i == value) return true;
for(Int32 i = start; i <= stop; i++) {
return false; if(i == value) {
} return true;
}
return int.Parse(parts) == value; }
}
} 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> /// <summary>
/// Extension methods. /// Gets the value if exists or default.
/// </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="defaultValue">The default value.</param>
/// <typeparam name="TKey">The type of the key.</typeparam> /// <returns>
/// <typeparam name="TValue">The type of the value.</typeparam> /// The value of the provided key or default.
/// <param name="dict">The dictionary.</param> /// </returns>
/// <param name="key">The key.</param> /// <exception cref="ArgumentNullException">dict.</exception>
/// <param name="defaultValue">The default value.</param> public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue defaultValue = default) {
/// <returns> if(dict == null) {
/// The value of the provided key or default. throw new ArgumentNullException(nameof(dict));
/// </returns> }
/// <exception cref="ArgumentNullException">dict.</exception>
public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue defaultValue = default) return dict.ContainsKey(key) ? dict[key] : defaultValue;
{ }
if (dict == null)
throw new ArgumentNullException(nameof(dict)); /// <summary>
/// Adds a key/value pair to the Dictionary if the key does not already exist.
return dict.ContainsKey(key) ? dict[key] : defaultValue; /// If the value is null, the key will not be updated.
} ///
/// Based on <c>ConcurrentDictionary.GetOrAdd</c> method.
/// <summary> /// </summary>
/// Adds a key/value pair to the Dictionary if the key does not already exist. /// <typeparam name="TKey">The type of the key.</typeparam>
/// If the value is null, the key will not be updated. /// <typeparam name="TValue">The type of the value.</typeparam>
/// /// <param name="dict">The dictionary.</param>
/// Based on <c>ConcurrentDictionary.GetOrAdd</c> method. /// <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="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.ContainsKey(key)) {
{ TValue value = valueFactory(key);
if (dict == null) if(Equals(value, default)) {
throw new ArgumentNullException(nameof(dict)); return default;
}
if (!dict.ContainsKey(key))
{ dict[key] = value;
var value = valueFactory(key); }
if (Equals(value, default)) return default;
dict[key] = value; return dict[key];
} }
return dict[key]; /// <summary>
} /// Executes the item action for each element in the Dictionary.
/// </summary>
/// <summary> /// <typeparam name="TKey">The type of the key.</typeparam>
/// Executes the item action for each element in the Dictionary. /// <typeparam name="TValue">The type of the value.</typeparam>
/// </summary> /// <param name="dict">The dictionary.</param>
/// <typeparam name="TKey">The type of the key.</typeparam> /// <param name="itemAction">The item action.</param>
/// <typeparam name="TValue">The type of the value.</typeparam> /// <exception cref="ArgumentNullException">dict.</exception>
/// <param name="dict">The dictionary.</param> public static void ForEach<TKey, TValue>(this IDictionary<TKey, TValue> dict, Action<TKey, TValue> itemAction) {
/// <param name="itemAction">The item action.</param> if(dict == null) {
/// <exception cref="ArgumentNullException">dict.</exception> throw new ArgumentNullException(nameof(dict));
public static void ForEach<TKey, TValue>(this IDictionary<TKey, TValue> dict, Action<TKey, TValue> itemAction) }
{
if (dict == null) foreach(KeyValuePair<TKey, TValue> kvp in dict) {
throw new ArgumentNullException(nameof(dict)); itemAction(kvp.Key, kvp.Value);
}
foreach (var 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> /// <summary>
/// Functional programming extension methods. /// Whens the specified condition.
/// </summary> /// </summary>
public static class FunctionalExtensions /// <typeparam name="T">The type of IQueryable.</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 IQueryable.
/// <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 IQueryable<T> When<T>(
/// condition this IQueryable<T> list,
/// or Func<Boolean> condition,
/// fn. Func<IQueryable<T>, IQueryable<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(condition == null) {
{ throw new ArgumentNullException(nameof(condition));
if (list == null) }
throw new ArgumentNullException(nameof(list));
if(fn == null) {
if (condition == null) throw new ArgumentNullException(nameof(fn));
throw new ArgumentNullException(nameof(condition)); }
if (fn == null) return condition() ? fn(list) : list;
throw new ArgumentNullException(nameof(fn)); }
return condition() ? fn(list) : list; /// <summary>
} /// Whens the specified condition.
/// </summary>
/// <summary> /// <typeparam name="T">The type of IEnumerable.</typeparam>
/// Whens the specified condition. /// <param name="list">The list.</param>
/// </summary> /// <param name="condition">The condition.</param>
/// <typeparam name="T">The type of IEnumerable.</typeparam> /// <param name="fn">The function.</param>
/// <param name="list">The list.</param> /// <returns>
/// <param name="condition">The condition.</param> /// The IEnumerable.
/// <param name="fn">The function.</param> /// </returns>
/// <returns> /// <exception cref="ArgumentNullException">
/// The IEnumerable. /// this
/// </returns> /// or
/// <exception cref="ArgumentNullException"> /// condition
/// this /// or
/// or /// fn.
/// condition /// </exception>
/// or public static IEnumerable<T> When<T>(
/// fn. this IEnumerable<T> list,
/// </exception> Func<Boolean> condition,
public static IEnumerable<T> When<T>( Func<IEnumerable<T>, IEnumerable<T>> fn) {
this IEnumerable<T> list, if(list == null) {
Func<bool> condition, throw new ArgumentNullException(nameof(list));
Func<IEnumerable<T>, IEnumerable<T>> fn) }
{
if (list == null) if(condition == null) {
throw new ArgumentNullException(nameof(list)); throw new ArgumentNullException(nameof(condition));
}
if (condition == null)
throw new ArgumentNullException(nameof(condition)); if(fn == null) {
throw new ArgumentNullException(nameof(fn));
if (fn == null) }
throw new ArgumentNullException(nameof(fn));
return condition() ? fn(list) : list;
return condition() ? fn(list) : list; }
}
/// <summary>
/// <summary> /// Adds the value when the condition is true.
/// Adds the value when the condition is true. /// </summary>
/// </summary> /// <typeparam name="T">The type of IList element.</typeparam>
/// <typeparam name="T">The type of IList element.</typeparam> /// <param name="list">The list.</param>
/// <param name="list">The list.</param> /// <param name="condition">The condition.</param>
/// <param name="condition">The condition.</param> /// <param name="value">The value.</param>
/// <param name="value">The value.</param> /// <returns>
/// <returns> /// The IList.
/// The IList. /// </returns>
/// </returns> /// <exception cref="ArgumentNullException">
/// <exception cref="ArgumentNullException"> /// this
/// this /// or
/// or /// condition
/// condition /// or
/// or /// value.
/// value. /// </exception>
/// </exception> public static IList<T> AddWhen<T>(
public static IList<T> AddWhen<T>( this IList<T> list,
this IList<T> list, Func<Boolean> condition,
Func<bool> condition, Func<T> value) {
Func<T> value) if(list == null) {
{ throw new ArgumentNullException(nameof(list));
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 (value == null) if(value == null) {
throw new ArgumentNullException(nameof(value)); throw new ArgumentNullException(nameof(value));
}
if (condition())
list.Add(value()); if(condition()) {
list.Add(value());
return list; }
}
return list;
/// <summary> }
/// Adds the value when the condition is true.
/// </summary> /// <summary>
/// <typeparam name="T">The type of IList element.</typeparam> /// Adds the value when the condition is true.
/// <param name="list">The list.</param> /// </summary>
/// <param name="condition">if set to <c>true</c> [condition].</param> /// <typeparam name="T">The type of IList element.</typeparam>
/// <param name="value">The value.</param> /// <param name="list">The list.</param>
/// <returns> /// <param name="condition">if set to <c>true</c> [condition].</param>
/// The IList. /// <param name="value">The value.</param>
/// </returns> /// <returns>
/// <exception cref="ArgumentNullException">list.</exception> /// The IList.
public static IList<T> AddWhen<T>( /// </returns>
this IList<T> list, /// <exception cref="ArgumentNullException">list.</exception>
bool condition, public static IList<T> AddWhen<T>(
T value) this IList<T> list,
{ Boolean condition,
if (list == null) T value) {
throw new ArgumentNullException(nameof(list)); if(list == null) {
throw new ArgumentNullException(nameof(list));
if (condition) }
list.Add(value);
if(condition) {
return list; list.Add(value);
} }
/// <summary> return list;
/// Adds the range when the condition is true. }
/// </summary>
/// <typeparam name="T">The type of List element.</typeparam> /// <summary>
/// <param name="list">The list.</param> /// Adds the range when the condition is true.
/// <param name="condition">The condition.</param> /// </summary>
/// <param name="value">The value.</param> /// <typeparam name="T">The type of List element.</typeparam>
/// <returns> /// <param name="list">The list.</param>
/// The List. /// <param name="condition">The condition.</param>
/// </returns> /// <param name="value">The value.</param>
/// <exception cref="ArgumentNullException"> /// <returns>
/// this /// The List.
/// or /// </returns>
/// condition /// <exception cref="ArgumentNullException">
/// or /// this
/// value. /// or
/// </exception> /// condition
public static List<T> AddRangeWhen<T>( /// or
this List<T> list, /// value.
Func<bool> condition, /// </exception>
Func<IEnumerable<T>> value) public static List<T> AddRangeWhen<T>(
{ this List<T> list,
if (list == null) Func<Boolean> condition,
throw new ArgumentNullException(nameof(list)); Func<IEnumerable<T>> value) {
if(list == null) {
if (condition == null) throw new ArgumentNullException(nameof(list));
throw new ArgumentNullException(nameof(condition)); }
if (value == null) if(condition == null) {
throw new ArgumentNullException(nameof(value)); throw new ArgumentNullException(nameof(condition));
}
if (condition())
list.AddRange(value()); if(value == null) {
throw new ArgumentNullException(nameof(value));
return list; }
}
} if(condition()) {
list.AddRange(value());
}
return list;
}
}
} }

View File

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

File diff suppressed because it is too large Load Diff

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> /// <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 /// <typeparam name="T">The type of value to clamp.</typeparam>
{ /// <param name="value">The value.</param>
/// <summary> /// <param name="min">The minimum.</param>
/// Clamps the specified value between the minimum and the maximum. /// <param name="max">The maximum.</param>
/// </summary> /// <returns>A value that indicates the relative order of the objects being compared.</returns>
/// <typeparam name="T">The type of value to clamp.</typeparam> public static T Clamp<T>(this T value, T min, T max)
/// <param name="value">The value.</param> where T : struct, IComparable => value.CompareTo(min) < 0 ? min : value.CompareTo(max) > 0 ? max : value;
/// <param name="min">The minimum.</param>
/// <param name="max">The maximum.</param> /// <summary>
/// <returns>A value that indicates the relative order of the objects being compared.</returns> /// Clamps the specified value between the minimum and the maximum.
public static T Clamp<T>(this T value, T min, T max) /// </summary>
where T : struct, IComparable /// <param name="value">The value.</param>
{ /// <param name="min">The minimum.</param>
if (value.CompareTo(min) < 0) return min; /// <param name="max">The maximum.</param>
/// <returns>A value that indicates the relative order of the objects being compared.</returns>
return value.CompareTo(max) > 0 ? max : value; public static Int32 Clamp(this Int32 value, Int32 min, Int32 max)
} => value < min ? min : (value > max ? max : value);
/// <summary> /// <summary>
/// Clamps the specified value between the minimum and the maximum. /// Determines whether the specified value is between a minimum and a maximum value.
/// </summary> /// </summary>
/// <param name="value">The value.</param> /// <typeparam name="T">The type of value to check.</typeparam>
/// <param name="min">The minimum.</param> /// <param name="value">The value.</param>
/// <param name="max">The maximum.</param> /// <param name="min">The minimum.</param>
/// <returns>A value that indicates the relative order of the objects being compared.</returns> /// <param name="max">The maximum.</param>
public static int Clamp(this int value, int min, int max) /// <returns>
=> value < min ? min : (value > max ? max : value); /// <c>true</c> if the specified minimum is between; otherwise, <c>false</c>.
/// </returns>
/// <summary> public static Boolean IsBetween<T>(this T value, T min, T max)
/// Determines whether the specified value is between a minimum and a maximum value. where T : struct, IComparable => value.CompareTo(min) >= 0 && value.CompareTo(max) <= 0;
/// </summary>
/// <typeparam name="T">The type of value to check.</typeparam> /// <summary>
/// <param name="value">The value.</param> /// Converts an array of bytes into the given struct type.
/// <param name="min">The minimum.</param> /// </summary>
/// <param name="max">The maximum.</param> /// <typeparam name="T">The type of structure to convert.</typeparam>
/// <returns> /// <param name="data">The data.</param>
/// <c>true</c> if the specified minimum is between; otherwise, <c>false</c>. /// <returns>a struct type derived from convert an array of bytes ref=ToStruct".</returns>
/// </returns> public static T ToStruct<T>(this Byte[] data)
public static bool IsBetween<T>(this T value, T min, T max) where T : struct => ToStruct<T>(data, 0, data.Length);
where T : struct, IComparable
{ /// <summary>
return value.CompareTo(min) >= 0 && value.CompareTo(max) <= 0; /// Converts an array of bytes into the given struct type.
} /// </summary>
/// <typeparam name="T">The type of structure to convert.</typeparam>
/// <summary> /// <param name="data">The data.</param>
/// Converts an array of bytes into the given struct type. /// <param name="offset">The offset.</param>
/// </summary> /// <param name="length">The length.</param>
/// <typeparam name="T">The type of structure to convert.</typeparam> /// <returns>
/// <param name="data">The data.</param> /// A managed object containing the data pointed to by the ptr parameter.
/// <returns>a struct type derived from convert an array of bytes ref=ToStruct".</returns> /// </returns>
public static T ToStruct<T>(this byte[] data) /// <exception cref="ArgumentNullException">data.</exception>
where T : struct public static T ToStruct<T>(this Byte[] data, Int32 offset, Int32 length)
{ where T : struct {
return ToStruct<T>(data, 0, data.Length); if(data == null) {
} throw new ArgumentNullException(nameof(data));
}
/// <summary>
/// Converts an array of bytes into the given struct type. Byte[] buffer = new Byte[length];
/// </summary> Array.Copy(data, offset, buffer, 0, buffer.Length);
/// <typeparam name="T">The type of structure to convert.</typeparam> GCHandle handle = GCHandle.Alloc(GetStructBytes<T>(buffer), GCHandleType.Pinned);
/// <param name="data">The data.</param>
/// <param name="offset">The offset.</param> try {
/// <param name="length">The length.</param> return Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());
/// <returns> } finally {
/// A managed object containing the data pointed to by the ptr parameter. handle.Free();
/// </returns> }
/// <exception cref="ArgumentNullException">data.</exception> }
public static T ToStruct<T>(this byte[] data, int offset, int length)
where T : struct /// <summary>
{ /// Converts a struct to an array of bytes.
if (data == null) /// </summary>
throw new ArgumentNullException(nameof(data)); /// <typeparam name="T">The type of structure to convert.</typeparam>
/// <param name="obj">The object.</param>
var buffer = new byte[length]; /// <returns>A byte array containing the results of encoding the specified set of characters.</returns>
Array.Copy(data, offset, buffer, 0, buffer.Length); public static Byte[] ToBytes<T>(this T obj)
var handle = GCHandle.Alloc(GetStructBytes<T>(buffer), GCHandleType.Pinned); where T : struct {
Byte[] data = new Byte[Marshal.SizeOf(obj)];
try GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
{
return Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject()); try {
} Marshal.StructureToPtr(obj, handle.AddrOfPinnedObject(), false);
finally return GetStructBytes<T>(data);
{ } 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) +
try ((longBytes & 0xff000000) >> 24));
{
Marshal.StructureToPtr(obj, handle.AddrOfPinnedObject(), false); private static Byte[] GetStructBytes<T>(Byte[] data) {
return GetStructBytes<T>(data); if(data == null) {
} throw new ArgumentNullException(nameof(data));
finally }
{
handle.Free(); #if !NETSTANDARD1_3
} FieldInfo[] fields = typeof(T).GetTypeInfo()
} .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
/// <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
var fields = typeof(T).GetTypeInfo()
.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(); Int32 offset = Marshal.OffsetOf<T>(field.Name).ToInt32();
var length = Marshal.SizeOf(field.FieldType); Int32 length = Marshal.SizeOf(field.FieldType);
endian = endian ?? Runtime.AttributeCache.RetrieveOne<StructEndiannessAttribute>(field); endian = endian ?? Runtime.AttributeCache.RetrieveOne<StructEndiannessAttribute>(field);
if (endian != null && (endian.Endianness == Endianness.Big && BitConverter.IsLittleEndian || if(endian != null && (endian.Endianness == Endianness.Big && BitConverter.IsLittleEndian ||
endian.Endianness == Endianness.Little && !BitConverter.IsLittleEndian)) endian.Endianness == Endianness.Little && !BitConverter.IsLittleEndian)) {
{ Array.Reverse(data, offset, length);
Array.Reverse(data, offset, length); }
} }
}
return data;
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> /// <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 /// <typeparam name="T">The type of the source.</typeparam>
{ /// <param name="source">The source.</param>
/// <summary> /// <param name="target">The target.</param>
/// Iterates over the public, instance, readable properties of the source and /// <returns>Number of properties that was copied successful.</returns>
/// tries to write a compatible value to a public, instance, writable property in the destination. public static Int32 CopyPropertiesTo<T>(this T source, Object target)
/// </summary> where T : class {
/// <typeparam name="T">The type of the source.</typeparam> IEnumerable<String> copyable = GetCopyableProperties(target);
/// <param name="source">The source.</param> return copyable.Any()
/// <param name="target">The target.</param> ? CopyOnlyPropertiesTo(source, target, copyable.ToArray())
/// <returns>Number of properties that was copied successful.</returns> : CopyPropertiesTo(source, target, null);
public static int CopyPropertiesTo<T>(this T source, object target) }
where T : class
{ /// <summary>
var copyable = GetCopyableProperties(target); /// Iterates over the public, instance, readable properties of the source and
return copyable.Any() /// tries to write a compatible value to a public, instance, writable property in the destination.
? CopyOnlyPropertiesTo(source, target, copyable.ToArray()) /// </summary>
: CopyPropertiesTo(source, target, null); /// <param name="source">The source.</param>
} /// <param name="target">The destination.</param>
/// <param name="ignoreProperties">The ignore properties.</param>
/// <summary> /// <returns>
/// Iterates over the public, instance, readable properties of the source and /// Number of properties that were successfully copied.
/// tries to write a compatible value to a public, instance, writable property in the destination. /// </returns>
/// </summary> 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 destination.</param>
/// <param name="ignoreProperties">The ignore properties.</param> /// <summary>
/// <returns> /// Iterates over the public, instance, readable properties of the source and
/// Number of properties that were successfully copied. /// tries to write a compatible value to a public, instance, writable property in the destination.
/// </returns> /// </summary>
public static int CopyPropertiesTo(this object source, object target, string[] ignoreProperties = null) /// <typeparam name="T">The type of the source.</typeparam>
=> Components.ObjectMapper.Copy(source, target, null, ignoreProperties); /// <param name="source">The source.</param>
/// <param name="target">The target.</param>
/// <summary> /// <returns>Number of properties that was copied successful.</returns>
/// Iterates over the public, instance, readable properties of the source and public static Int32 CopyOnlyPropertiesTo<T>(this T source, Object target)
/// tries to write a compatible value to a public, instance, writable property in the destination. where T : class => CopyOnlyPropertiesTo(source, target, null);
/// </summary>
/// <typeparam name="T">The type of the source.</typeparam> /// <summary>
/// <param name="source">The source.</param> /// Iterates over the public, instance, readable properties of the source and
/// <param name="target">The target.</param> /// tries to write a compatible value to a public, instance, writable property in the destination.
/// <returns>Number of properties that was copied successful.</returns> /// </summary>
public static int CopyOnlyPropertiesTo<T>(this T source, object target) /// <param name="source">The source.</param>
where T : class /// <param name="target">The destination.</param>
{ /// <param name="propertiesToCopy">Properties to copy.</param>
return CopyOnlyPropertiesTo(source, target, null); /// <returns>
} /// Number of properties that were successfully copied.
/// </returns>
/// <summary> public static Int32 CopyOnlyPropertiesTo(this Object source, Object target, String[] propertiesToCopy) => Components.ObjectMapper.Copy(source, target, propertiesToCopy);
/// 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> /// Copies the properties to new instance of T.
/// <param name="source">The source.</param> /// </summary>
/// <param name="target">The destination.</param> /// <typeparam name="T">The new object type.</typeparam>
/// <param name="propertiesToCopy">Properties to copy.</param> /// <param name="source">The source.</param>
/// <returns> /// <param name="ignoreProperties">The ignore properties.</param>
/// Number of properties that were successfully copied. /// <returns>
/// </returns> /// The specified type with properties copied.
public static int CopyOnlyPropertiesTo(this object source, object target, string[] propertiesToCopy) /// </returns>
{ /// <exception cref="ArgumentNullException">source.</exception>
return Components.ObjectMapper.Copy(source, target, propertiesToCopy); public static T DeepClone<T>(this T source, String[] ignoreProperties = null)
} where T : class => source.CopyPropertiesToNew<T>(ignoreProperties);
/// <summary> /// <summary>
/// Copies the properties to new instance of T. /// Copies the properties to new instance of T.
/// </summary> /// </summary>
/// <typeparam name="T">The new object type.</typeparam> /// <typeparam name="T">The new object type.</typeparam>
/// <param name="source">The source.</param> /// <param name="source">The source.</param>
/// <param name="ignoreProperties">The ignore properties.</param> /// <param name="ignoreProperties">The ignore properties.</param>
/// <returns> /// <returns>
/// The specified type with properties copied. /// The specified type with properties copied.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">source.</exception> /// <exception cref="ArgumentNullException">source.</exception>
public static T DeepClone<T>(this T source, string[] ignoreProperties = null) public static T CopyPropertiesToNew<T>(this Object source, String[] ignoreProperties = null)
where T : class where T : class {
{ if(source == null) {
return source.CopyPropertiesToNew<T>(ignoreProperties); throw new ArgumentNullException(nameof(source));
} }
/// <summary> T target = Activator.CreateInstance<T>();
/// Copies the properties to new instance of T. IEnumerable<String> copyable = target.GetCopyableProperties();
/// </summary>
/// <typeparam name="T">The new object type.</typeparam> _ = copyable.Any() ? source.CopyOnlyPropertiesTo(target, copyable.ToArray()) : source.CopyPropertiesTo(target, ignoreProperties);
/// <param name="source">The source.</param>
/// <param name="ignoreProperties">The ignore properties.</param> return target;
/// <returns> }
/// The specified type with properties copied.
/// </returns> /// <summary>
/// <exception cref="ArgumentNullException">source.</exception> /// Copies the only properties to new instance of T.
public static T CopyPropertiesToNew<T>(this object source, string[] ignoreProperties = null) /// </summary>
where T : class /// <typeparam name="T">Object Type.</typeparam>
{ /// <param name="source">The source.</param>
if (source == null) /// <param name="propertiesToCopy">The properties to copy.</param>
throw new ArgumentNullException(nameof(source)); /// <returns>
/// The specified type with properties copied.
var target = Activator.CreateInstance<T>(); /// </returns>
var copyable = target.GetCopyableProperties(); /// <exception cref="ArgumentNullException">source.</exception>
public static T CopyOnlyPropertiesToNew<T>(this Object source, String[] propertiesToCopy)
if (copyable.Any()) where T : class {
source.CopyOnlyPropertiesTo(target, copyable.ToArray()); if(source == null) {
else throw new ArgumentNullException(nameof(source));
source.CopyPropertiesTo(target, ignoreProperties); }
return target; T target = Activator.CreateInstance<T>();
} _ = source.CopyOnlyPropertiesTo(target, propertiesToCopy);
return target;
/// <summary> }
/// Copies the only properties to new instance of T.
/// </summary> /// <summary>
/// <typeparam name="T">Object Type.</typeparam> /// Iterates over the keys of the source and tries to write a compatible value to a public,
/// <param name="source">The source.</param> /// instance, writable property in the destination.
/// <param name="propertiesToCopy">The properties to copy.</param> /// </summary>
/// <returns> /// <param name="source">The source.</param>
/// The specified type with properties copied. /// <param name="target">The target.</param>
/// </returns> /// <param name="ignoreKeys">The ignore keys.</param>
/// <exception cref="ArgumentNullException">source.</exception> /// <returns>Number of properties that was copied successful.</returns>
public static T CopyOnlyPropertiesToNew<T>(this object source, string[] propertiesToCopy) public static Int32 CopyKeyValuePairTo(
where T : class this IDictionary<String, Object> source,
{ Object target,
if (source == null) String[] ignoreKeys = null) => Components.ObjectMapper.Copy(source, target, null, ignoreKeys);
throw new ArgumentNullException(nameof(source));
/// <summary>
var target = Activator.CreateInstance<T>(); /// Measures the elapsed time of the given action as a TimeSpan
source.CopyOnlyPropertiesTo(target, propertiesToCopy); /// This method uses a high precision Stopwatch.
return target; /// </summary>
} /// <param name="target">The target.</param>
/// <returns>
/// <summary> /// A time interval that represents a specified time, where the specification is in units of ticks.
/// Iterates over the keys of the source and tries to write a compatible value to a public, /// </returns>
/// instance, writable property in the destination. /// <exception cref="ArgumentNullException">target.</exception>
/// </summary> public static TimeSpan Benchmark(this Action target) {
/// <param name="source">The source.</param> if(target == null) {
/// <param name="target">The target.</param> throw new ArgumentNullException(nameof(target));
/// <param name="ignoreKeys">The ignore keys.</param> }
/// <returns>Number of properties that was copied successful.</returns>
public static int CopyKeyValuePairTo( Stopwatch sw = new Stopwatch();
this IDictionary<string, object> source,
object target, try {
string[] ignoreKeys = null) sw.Start();
{ target.Invoke();
return Components.ObjectMapper.Copy(source, target, null, ignoreKeys); } catch {
} // swallow
} finally {
/// <summary> sw.Stop();
/// Measures the elapsed time of the given action as a TimeSpan }
/// This method uses a high precision Stopwatch.
/// </summary> return TimeSpan.FromTicks(sw.ElapsedTicks);
/// <param name="target">The target.</param> }
/// <returns>
/// A time interval that represents a specified time, where the specification is in units of ticks. /// <summary>
/// </returns> /// Does the specified action.
/// <exception cref="ArgumentNullException">target.</exception> /// </summary>
public static TimeSpan Benchmark(this Action target) /// <param name="action">The action.</param>
{ /// <param name="retryInterval">The retry interval.</param>
if (target == null) /// <param name="retryCount">The retry count.</param>
throw new ArgumentNullException(nameof(target)); public static void Retry(
this Action action,
var sw = new Stopwatch(); TimeSpan retryInterval = default,
Int32 retryCount = 3) {
try if(action == null) {
{ throw new ArgumentNullException(nameof(action));
sw.Start(); }
target.Invoke();
} _ = Retry<Object>(() => {
catch action();
{ return null;
// swallow },
} retryInterval,
finally retryCount);
{ }
sw.Stop();
} /// <summary>
/// Does the specified action.
return TimeSpan.FromTicks(sw.ElapsedTicks); /// </summary>
} /// <typeparam name="T">The type of the source.</typeparam>
/// <param name="action">The action.</param>
/// <summary> /// <param name="retryInterval">The retry interval.</param>
/// Does the specified action. /// <param name="retryCount">The retry count.</param>
/// </summary> /// <returns>
/// <param name="action">The action.</param> /// The return value of the method that this delegate encapsulates.
/// <param name="retryInterval">The retry interval.</param> /// </returns>
/// <param name="retryCount">The retry count.</param> /// <exception cref="ArgumentNullException">action.</exception>
public static void Retry( /// <exception cref="AggregateException">Represents one or many errors that occur during application execution.</exception>
this Action action, public static T Retry<T>(
TimeSpan retryInterval = default, this Func<T> action,
int retryCount = 3) TimeSpan retryInterval = default,
{ Int32 retryCount = 3) {
if (action == null) if(action == null) {
throw new ArgumentNullException(nameof(action)); throw new ArgumentNullException(nameof(action));
}
Retry<object>(() =>
{ if(retryInterval == default) {
action(); retryInterval = TimeSpan.FromSeconds(1);
return null; }
},
retryInterval, List<Exception> exceptions = new List<Exception>();
retryCount);
} for(Int32 retry = 0; retry < retryCount; retry++) {
try {
/// <summary> if(retry > 0) {
/// Does the specified action. Task.Delay(retryInterval).Wait();
/// </summary> }
/// <typeparam name="T">The type of the source.</typeparam>
/// <param name="action">The action.</param> return action();
/// <param name="retryInterval">The retry interval.</param> } catch(Exception ex) {
/// <param name="retryCount">The retry count.</param> exceptions.Add(ex);
/// <returns> }
/// The return value of the method that this delegate encapsulates. }
/// </returns>
/// <exception cref="ArgumentNullException">action.</exception> throw new AggregateException(exceptions);
/// <exception cref="AggregateException">Represents one or many errors that occur during application execution.</exception> }
public static T Retry<T>(
this Func<T> action, /// <summary>
TimeSpan retryInterval = default, /// Retrieves the exception message, plus all the inner exception messages separated by new lines.
int retryCount = 3) /// </summary>
{ /// <param name="ex">The ex.</param>
if (action == null) /// <param name="priorMessage">The prior message.</param>
throw new ArgumentNullException(nameof(action)); /// <returns>A <see cref="System.String" /> that represents this instance.</returns>
public static String ExceptionMessage(this Exception ex, String priorMessage = "") {
if (retryInterval == default) while(true) {
retryInterval = TimeSpan.FromSeconds(1); if(ex == null) {
throw new ArgumentNullException(nameof(ex));
var exceptions = new List<Exception>(); }
for (var retry = 0; retry < retryCount; retry++) String fullMessage = String.IsNullOrWhiteSpace(priorMessage)
{
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; ex = ex.InnerException;
} priorMessage = fullMessage;
} }
}
/// <summary>
/// Gets the copyable properties. /// <summary>
/// </summary> /// Gets the copyable properties.
/// <param name="obj">The object.</param> /// </summary>
/// <returns> /// <param name="obj">The object.</param>
/// Array of properties. /// <returns>
/// </returns> /// Array of properties.
/// <exception cref="ArgumentNullException">model.</exception> /// </returns>
public static IEnumerable<string> GetCopyableProperties(this object obj) /// <exception cref="ArgumentNullException">model.</exception>
{ public static IEnumerable<String> GetCopyableProperties(this Object obj) {
if (obj == null) if(obj == null) {
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
}
return Runtime.PropertyTypeCache
.RetrieveAllProperties(obj.GetType(), true) return Runtime.PropertyTypeCache
.Select(x => new { x.Name, HasAttribute = Runtime.AttributeCache.RetrieveOne<CopyableAttribute>(x) != null}) .RetrieveAllProperties(obj.GetType(), true)
.Where(x => x.HasAttribute) .Select(x => new { x.Name, HasAttribute = Runtime.AttributeCache.RetrieveOne<CopyableAttribute>(x) != null })
.Select(x => x.Name); .Where(x => x.HasAttribute)
} .Select(x => x.Name);
}
/// <summary>
/// Returns true if the object is valid. /// <summary>
/// </summary> /// Returns true if the object is valid.
/// <param name="obj">The object.</param> /// </summary>
/// <returns> /// <param name="obj">The object.</param>
/// <c>true</c> if the specified model is valid; otherwise, <c>false</c>. /// <returns>
/// </returns> /// <c>true</c> if the specified model is valid; otherwise, <c>false</c>.
public static bool IsValid(this object obj) => Runtime.ObjectValidator.IsValid(obj); /// </returns>
public static Boolean IsValid(this Object obj) => Runtime.ObjectValidator.IsValid(obj);
internal static void CreateTarget(
this object source, internal static void CreateTarget(
Type targetType, this Object source,
bool includeNonPublic, Type targetType,
ref object target) Boolean includeNonPublic,
{ ref Object target) {
switch (source) switch(source) {
{ case String _:
case string _: break; // do nothing. Simply skip creation
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
case IList sourceObjectList when targetType.IsArray: // When using arrays, there is no default constructor, attempt to build a compatible array Type elementType = targetType.GetElementType();
var elementType = targetType.GetElementType();
if(elementType != null) {
if (elementType != null) target = Array.CreateInstance(elementType, sourceObjectList.Count);
target = Array.CreateInstance(elementType, sourceObjectList.Count); }
break;
default: break;
target = Activator.CreateInstance(targetType, includeNonPublic); default:
break; 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
/// { /// <summary>
/// public string Name { get; set; } /// Initializes a new instance of the <see cref="CsvWriter"/> class.
/// public int Age { get; set; } /// It automatically closes the stream when disposing this writer.
/// } /// </summary>
/// /// <param name="outputStream">The output stream.</param>
/// static void Main() /// <param name="encoding">The encoding.</param>
/// { public CsvWriter(Stream outputStream, Encoding encoding)
/// // create a list of people : this(outputStream, false, encoding) {
/// var people = new List&lt;Person&gt; // placeholder
/// { }
/// new Person { Name = "Artyom", Age = 20 },
/// new Person { Name = "Aloy", Age = 18 } /// <summary>
/// } /// Initializes a new instance of the <see cref="CsvWriter"/> class.
/// /// It uses the Windows 1252 encoding and automatically closes
/// // write items inside file.csv /// the stream upon disposing this writer.
/// CsvWriter.SaveRecords(people, "C:\\Users\\user\\Documents\\file.csv"); /// </summary>
/// /// <param name="outputStream">The output stream.</param>
/// // output public CsvWriter(Stream outputStream)
/// // | Name | Age | : this(outputStream, false, Definitions.Windows1252Encoding) {
/// // | Artyom | 20 | // placeholder
/// // | Aloy | 18 | }
/// }
/// } /// <summary>
/// </code> /// Initializes a new instance of the <see cref="CsvWriter"/> class.
/// </example> /// It opens the file given file, automatically closes the stream upon
public class CsvWriter : IDisposable /// disposing of this writer, and uses the Windows 1252 encoding.
{ /// </summary>
private static readonly PropertyTypeCache TypeCache = new PropertyTypeCache(); /// <param name="filename">The filename.</param>
public CsvWriter(String filename)
private readonly object _syncLock = new object(); : this(File.OpenWrite(filename), false, Definitions.Windows1252Encoding) {
private readonly Stream _outputStream; // placeholder
private readonly Encoding _encoding; }
private readonly bool _leaveStreamOpen;
private bool _isDisposing; /// <summary>
private ulong _mCount; /// Initializes a new instance of the <see cref="CsvWriter"/> class.
/// It opens the file given file, automatically closes the stream upon
#region Constructors /// disposing of this writer, and uses the given text encoding for output.
/// </summary>
/// <summary> /// <param name="filename">The filename.</param>
/// Initializes a new instance of the <see cref="CsvWriter" /> class. /// <param name="encoding">The encoding.</param>
/// </summary> public CsvWriter(String filename, Encoding encoding)
/// <param name="outputStream">The output stream.</param> : this(File.OpenWrite(filename), false, encoding) {
/// <param name="leaveOpen">if set to <c>true</c> [leave open].</param> // placeholder
/// <param name="encoding">The encoding.</param> }
public CsvWriter(Stream outputStream, bool leaveOpen, Encoding encoding)
{ #endregion
_outputStream = outputStream;
_encoding = encoding; #region Properties
_leaveStreamOpen = leaveOpen;
} /// <summary>
/// Gets or sets the field separator character.
/// <summary> /// </summary>
/// Initializes a new instance of the <see cref="CsvWriter"/> class. /// <value>
/// It automatically closes the stream when disposing this writer. /// The separator character.
/// </summary> /// </value>
/// <param name="outputStream">The output stream.</param> public Char SeparatorCharacter { get; set; } = ',';
/// <param name="encoding">The encoding.</param>
public CsvWriter(Stream outputStream, Encoding encoding) /// <summary>
: this(outputStream, false, encoding) /// Gets or sets the escape character to use to escape field values.
{ /// </summary>
// placeholder /// <value>
} /// The escape character.
/// </value>
/// <summary> public Char EscapeCharacter { get; set; } = '"';
/// Initializes a new instance of the <see cref="CsvWriter"/> class.
/// It uses the Windows 1252 encoding and automatically closes /// <summary>
/// the stream upon disposing this writer. /// Gets or sets the new line character sequence to use when writing a line.
/// </summary> /// </summary>
/// <param name="outputStream">The output stream.</param> /// <value>
public CsvWriter(Stream outputStream) /// The new line sequence.
: this(outputStream, false, Definitions.Windows1252Encoding) /// </value>
{ public String NewLineSequence { get; set; } = Environment.NewLine;
// placeholder
} /// <summary>
/// Defines a list of properties to ignore when outputting CSV lines.
/// <summary> /// </summary>
/// Initializes a new instance of the <see cref="CsvWriter"/> class. /// <value>
/// It opens the file given file, automatically closes the stream upon /// The ignore property names.
/// disposing of this writer, and uses the Windows 1252 encoding. /// </value>
/// </summary> public List<String> IgnorePropertyNames { get; } = new List<String>();
/// <param name="filename">The filename.</param>
public CsvWriter(string filename) /// <summary>
: this(File.OpenWrite(filename), false, Definitions.Windows1252Encoding) /// Gets number of lines that have been written, including the headings line.
{ /// </summary>
// placeholder /// <value>
} /// The count.
/// </value>
/// <summary> public UInt64 Count {
/// Initializes a new instance of the <see cref="CsvWriter"/> class. get {
/// It opens the file given file, automatically closes the stream upon lock(this._syncLock) {
/// disposing of this writer, and uses the given text encoding for output. return this._mCount;
/// </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) #endregion
{
// placeholder #region Helpers
}
/// <summary>
#endregion /// Saves the items to a stream.
/// It uses the Windows 1252 text encoding for output.
#region Properties /// </summary>
/// <typeparam name="T">The type of enumeration.</typeparam>
/// <summary> /// <param name="items">The items.</param>
/// Gets or sets the field separator character. /// <param name="stream">The stream.</param>
/// </summary> /// <param name="truncateData"><c>true</c> if stream is truncated, default <c>false</c>.</param>
/// <value> /// <returns>Number of item saved.</returns>
/// The separator character. public static Int32 SaveRecords<T>(IEnumerable<T> items, Stream stream, Boolean truncateData = false) {
/// </value> // truncate the file if it had data
public char SeparatorCharacter { get; set; } = ','; if(truncateData && stream.Length > 0) {
stream.SetLength(0);
/// <summary> }
/// Gets or sets the escape character to use to escape field values.
/// </summary> using(CsvWriter writer = new CsvWriter(stream)) {
/// <value> writer.WriteHeadings<T>();
/// The escape character. writer.WriteObjects(items);
/// </value> return (Int32)writer.Count;
public char EscapeCharacter { get; set; } = '"'; }
}
/// <summary>
/// Gets or sets the new line character sequence to use when writing a line. /// <summary>
/// </summary> /// Saves the items to a CSV file.
/// <value> /// If the file exits, it overwrites it. If it does not, it creates it.
/// The new line sequence. /// It uses the Windows 1252 text encoding for output.
/// </value> /// </summary>
public string NewLineSequence { get; set; } = Environment.NewLine; /// <typeparam name="T">The type of enumeration.</typeparam>
/// <param name="items">The items.</param>
/// <summary> /// <param name="filePath">The file path.</param>
/// Defines a list of properties to ignore when outputting CSV lines. /// <returns>Number of item saved.</returns>
/// </summary> public static Int32 SaveRecords<T>(IEnumerable<T> items, String filePath) => SaveRecords(items, File.OpenWrite(filePath), true);
/// <value>
/// The ignore property names. #endregion
/// </value>
public List<string> IgnorePropertyNames { get; } = new List<string>(); #region Generic, main Write Line Method
/// <summary> /// <summary>
/// Gets number of lines that have been written, including the headings line. /// Writes a line of CSV text. Items are converted to strings.
/// </summary> /// If items are found to be null, empty strings are written out.
/// <value> /// If items are not string, the ToStringInvariant() method is called on them.
/// The count. /// </summary>
/// </value> /// <param name="items">The items.</param>
public ulong Count public void WriteLine(params Object[] items)
{ => this.WriteLine(items.Select(x => x == null ? String.Empty : x.ToStringInvariant()));
get
{ /// <summary>
lock (_syncLock) /// Writes a line of CSV text. Items are converted to strings.
{ /// If items are found to be null, empty strings are written out.
return _mCount; /// 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()));
#endregion
/// <summary>
#region Helpers /// Writes a line of CSV text.
/// If items are found to be null, empty strings are written out.
/// <summary> /// </summary>
/// Saves the items to a stream. /// <param name="items">The items.</param>
/// It uses the Windows 1252 text encoding for output. public void WriteLine(params String[] items) => this.WriteLine((IEnumerable<String>)items);
/// </summary>
/// <typeparam name="T">The type of enumeration.</typeparam> /// <summary>
/// <param name="items">The items.</param> /// Writes a line of CSV text.
/// <param name="stream">The stream.</param> /// If items are found to be null, empty strings are written out.
/// <param name="truncateData"><c>true</c> if stream is truncated, default <c>false</c>.</param> /// </summary>
/// <returns>Number of item saved.</returns> /// <param name="items">The items.</param>
public static int SaveRecords<T>(IEnumerable<T> items, Stream stream, bool truncateData = false) public void WriteLine(IEnumerable<String> items) {
{ lock(this._syncLock) {
// truncate the file if it had data Int32 length = items.Count();
if (truncateData && stream.Length > 0) Byte[] separatorBytes = this._encoding.GetBytes(new[] { this.SeparatorCharacter });
stream.SetLength(0); Byte[] endOfLineBytes = this._encoding.GetBytes(this.NewLineSequence);
using (var writer = new CsvWriter(stream)) // Declare state variables here to avoid recreation, allocation and
{ // reassignment in every loop
writer.WriteHeadings<T>(); Boolean needsEnclosing;
writer.WriteObjects(items); String textValue;
return (int)writer.Count; Byte[] output;
}
} for(Int32 i = 0; i < length; i++) {
textValue = items.ElementAt(i);
/// <summary>
/// Saves the items to a CSV file. // Determine if we need the string to be enclosed
/// If the file exits, it overwrites it. If it does not, it creates it. // (it either contains an escape, new line, or separator char)
/// It uses the Windows 1252 text encoding for output. needsEnclosing = textValue.IndexOf(this.SeparatorCharacter) >= 0
/// </summary> || textValue.IndexOf(this.EscapeCharacter) >= 0
/// <typeparam name="T">The type of enumeration.</typeparam> || textValue.IndexOf('\r') >= 0
/// <param name="items">The items.</param> || textValue.IndexOf('\n') >= 0;
/// <param name="filePath">The file path.</param>
/// <returns>Number of item saved.</returns> // Escape the escape characters by repeating them twice for every instance
public static int SaveRecords<T>(IEnumerable<T> items, string filePath) => SaveRecords(items, File.OpenWrite(filePath), true); textValue = textValue.Replace($"{this.EscapeCharacter}",
$"{this.EscapeCharacter}{this.EscapeCharacter}");
#endregion
// Enclose the text value if we need to
#region Generic, main Write Line Method if(needsEnclosing) {
textValue = String.Format($"{this.EscapeCharacter}{textValue}{this.EscapeCharacter}", textValue);
/// <summary> }
/// Writes a line of CSV text. Items are converted to strings.
/// If items are found to be null, empty strings are written out. // Get the bytes to write to the stream and write them
/// If items are not string, the ToStringInvariant() method is called on them. output = this._encoding.GetBytes(textValue);
/// </summary> this._outputStream.Write(output, 0, output.Length);
/// <param name="items">The items.</param>
public void WriteLine(params object[] items) // only write a separator if we are moving in between values.
=> WriteLine(items.Select(x => x == null ? string.Empty : x.ToStringInvariant())); // the last value should not be written.
if(i < length - 1) {
/// <summary> this._outputStream.Write(separatorBytes, 0, separatorBytes.Length);
/// 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> // output the newline sequence
/// <param name="items">The items.</param> this._outputStream.Write(endOfLineBytes, 0, endOfLineBytes.Length);
public void WriteLine(IEnumerable<object> items) this._mCount += 1;
=> WriteLine(items.Select(x => x == null ? string.Empty : x.ToStringInvariant())); }
}
/// <summary>
/// Writes a line of CSV text. #endregion
/// If items are found to be null, empty strings are written out.
/// </summary> #region Write Object Method
/// <param name="items">The items.</param>
public void WriteLine(params string[] items) => WriteLine((IEnumerable<string>) items); /// <summary>
/// Writes a row of CSV text. It handles the special cases where the object is
/// <summary> /// a dynamic object or and array. It also handles non-collection objects fine.
/// Writes a line of CSV text. /// If you do not like the way the output is handled, you can simply write an extension
/// If items are found to be null, empty strings are written out. /// method of this class and use the WriteLine method instead.
/// </summary> /// </summary>
/// <param name="items">The items.</param> /// <param name="item">The item.</param>
public void WriteLine(IEnumerable<string> items) /// <exception cref="System.ArgumentNullException">item.</exception>
{ public void WriteObject(Object item) {
lock (_syncLock) if(item == null) {
{ throw new ArgumentNullException(nameof(item));
var length = items.Count(); }
var separatorBytes = _encoding.GetBytes(new[] { SeparatorCharacter });
var endOfLineBytes = _encoding.GetBytes(NewLineSequence); lock(this._syncLock) {
switch(item) {
// Declare state variables here to avoid recreation, allocation and case IDictionary typedItem:
// reassignment in every loop this.WriteLine(this.GetFilteredDictionary(typedItem));
bool needsEnclosing; return;
string textValue; case ICollection typedItem:
byte[] output; this.WriteLine(typedItem.Cast<Object>());
return;
for (var i = 0; i < length; i++) default:
{ this.WriteLine(this.GetFilteredTypeProperties(item.GetType())
textValue = items.ElementAt(i); .Select(x => x.ToFormattedString(item)));
break;
// 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 /// <summary>
|| textValue.IndexOf('\n') >= 0; /// 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.
// Escape the escape characters by repeating them twice for every instance /// If you do not like the way the output is handled, you can simply write an extension
textValue = textValue.Replace($"{EscapeCharacter}", /// method of this class and use the WriteLine method instead.
$"{EscapeCharacter}{EscapeCharacter}"); /// </summary>
/// <typeparam name="T">The type of object to write.</typeparam>
// Enclose the text value if we need to /// <param name="item">The item.</param>
if (needsEnclosing) public void WriteObject<T>(T item) => this.WriteObject(item as Object);
textValue = string.Format($"{EscapeCharacter}{textValue}{EscapeCharacter}", textValue);
/// <summary>
// Get the bytes to write to the stream and write them /// Writes a set of items, one per line and atomically by repeatedly calling the
output = _encoding.GetBytes(textValue); /// WriteObject method. For more info check out the description of the WriteObject
_outputStream.Write(output, 0, output.Length); /// method.
/// </summary>
// only write a separator if we are moving in between values. /// <typeparam name="T">The type of object to write.</typeparam>
// the last value should not be written. /// <param name="items">The items.</param>
if (i < length - 1) public void WriteObjects<T>(IEnumerable<T> items) {
_outputStream.Write(separatorBytes, 0, separatorBytes.Length); lock(this._syncLock) {
} foreach(T item in items) {
this.WriteObject(item);
// output the newline sequence }
_outputStream.Write(endOfLineBytes, 0, endOfLineBytes.Length); }
_mCount += 1; }
}
} #endregion
#endregion #region Write Headings Methods
#region Write Object Method /// <summary>
/// Writes the headings.
/// <summary> /// </summary>
/// Writes a row of CSV text. It handles the special cases where the object is /// <param name="type">The type of object to extract headings.</param>
/// a dynamic object or and array. It also handles non-collection objects fine. /// <exception cref="System.ArgumentNullException">type.</exception>
/// If you do not like the way the output is handled, you can simply write an extension public void WriteHeadings(Type type) {
/// method of this class and use the WriteLine method instead. if(type == null) {
/// </summary> throw new ArgumentNullException(nameof(type));
/// <param name="item">The item.</param> }
/// <exception cref="System.ArgumentNullException">item.</exception>
public void WriteObject(object item) IEnumerable<Object> properties = this.GetFilteredTypeProperties(type).Select(p => p.Name).Cast<Object>();
{ this.WriteLine(properties);
if (item == null) }
throw new ArgumentNullException(nameof(item));
/// <summary>
lock (_syncLock) /// Writes the headings.
{ /// </summary>
switch (item) /// <typeparam name="T">The type of object to extract headings.</typeparam>
{ public void WriteHeadings<T>() => this.WriteHeadings(typeof(T));
case IDictionary typedItem:
WriteLine(GetFilteredDictionary(typedItem)); /// <summary>
return; /// Writes the headings.
case ICollection typedItem: /// </summary>
WriteLine(typedItem.Cast<object>()); /// <param name="dictionary">The dictionary to extract headings.</param>
return; /// <exception cref="System.ArgumentNullException">dictionary.</exception>
default: public void WriteHeadings(IDictionary dictionary) {
WriteLine(GetFilteredTypeProperties(item.GetType()) if(dictionary == null) {
.Select(x => x.ToFormattedString(item))); throw new ArgumentNullException(nameof(dictionary));
break; }
}
} this.WriteLine(this.GetFilteredDictionary(dictionary, true));
} }
/// <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>
/// <typeparam name="T">The type of object to write.</typeparam>
/// <param name="item">The item.</param>
public void WriteObject<T>(T item) => WriteObject(item as object);
/// <summary>
/// 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
/// method.
/// </summary>
/// <typeparam name="T">The type of object to write.</typeparam>
/// <param name="items">The items.</param>
public void WriteObjects<T>(IEnumerable<T> items)
{
lock (_syncLock)
{
foreach (var item in items)
WriteObject(item);
}
}
#endregion
#region Write Headings Methods
/// <summary>
/// Writes the headings.
/// </summary>
/// <param name="type">The type of object to extract headings.</param>
/// <exception cref="System.ArgumentNullException">type.</exception>
public void WriteHeadings(Type type)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
var properties = GetFilteredTypeProperties(type).Select(p => p.Name).Cast<object>();
WriteLine(properties);
}
/// <summary>
/// Writes the headings.
/// </summary>
/// <typeparam name="T">The type of object to extract headings.</typeparam>
public void WriteHeadings<T>() => WriteHeadings(typeof(T));
/// <summary>
/// Writes the headings.
/// </summary>
/// <param name="dictionary">The dictionary to extract headings.</param>
/// <exception cref="System.ArgumentNullException">dictionary.</exception>
public void WriteHeadings(IDictionary dictionary)
{
if (dictionary == null)
throw new ArgumentNullException(nameof(dictionary));
WriteLine(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.
@ -424,55 +410,54 @@
WriteHeadings(obj.GetType()); WriteHeadings(obj.GetType());
} }
#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 (_leaveStreamOpen == false) if(this._leaveStreamOpen == false) {
{ this._outputStream.Dispose();
_outputStream.Dispose(); }
} }
}
this._isDisposing = true;
_isDisposing = true; }
}
#endregion
#endregion
#region Support Methods
#region Support Methods
private IEnumerable<String> GetFilteredDictionary(IDictionary dictionary, Boolean filterKeys = false)
private IEnumerable<string> GetFilteredDictionary(IDictionary dictionary, bool filterKeys = false) => dictionary
=> dictionary .Keys
.Keys .Cast<Object>()
.Cast<object>() .Select(key => key == null ? String.Empty : key.ToStringInvariant())
.Select(key => key == null ? string.Empty : key.ToStringInvariant()) .Where(stringKey => !this.IgnorePropertyNames.Contains(stringKey))
.Where(stringKey => !IgnorePropertyNames.Contains(stringKey)) .Select(stringKey =>
.Select(stringKey => filterKeys
filterKeys ? stringKey
? stringKey : dictionary[stringKey] == null ? String.Empty : dictionary[stringKey].ToStringInvariant());
: dictionary[stringKey] == null ? string.Empty : dictionary[stringKey].ToStringInvariant());
private IEnumerable<PropertyInfo> GetFilteredTypeProperties(Type type)
private IEnumerable<PropertyInfo> GetFilteredTypeProperties(Type type) => TypeCache.Retrieve(type, t =>
=> TypeCache.Retrieve(type, t => t.GetProperties(BindingFlags.Public | BindingFlags.Instance)
t.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.CanRead))
.Where(p => p.CanRead)) .Where(p => !this.IgnorePropertyNames.Contains(p.Name));
.Where(p => !IgnorePropertyNames.Contains(p.Name));
#endregion
#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;
namespace Unosquare.Swan.Formatters {
internal class HumanizeJson internal class HumanizeJson {
{ private readonly StringBuilder _builder = new StringBuilder();
private readonly StringBuilder _builder = new StringBuilder(); private readonly Int32 _indent;
private readonly int _indent; private readonly String _indentStr;
private readonly string _indentStr; private readonly Object _obj;
private readonly object _obj;
public HumanizeJson(Object obj, Int32 indent) {
public HumanizeJson(object obj, int indent) if(obj == null) {
{ return;
if (obj == null) }
{
return; this._indent = indent;
} this._indentStr = new String(' ', indent * 4);
this._obj = obj;
_indent = indent;
_indentStr = new string(' ', indent * 4); this.ParseObject();
_obj = obj; }
ParseObject(); public String GetResult() => this._builder == null ? String.Empty : this._builder.ToString().TrimEnd();
}
private void ParseObject() {
public string GetResult() => _builder == null ? string.Empty : _builder.ToString().TrimEnd(); switch(this._obj) {
case Dictionary<String, Object> dictionary:
private void ParseObject() this.AppendDictionary(dictionary);
{ break;
switch (_obj) case List<Object> list:
{ this.AppendList(list);
case Dictionary<string, object> dictionary: break;
AppendDictionary(dictionary); default:
break; this.AppendString();
case List<object> list: break;
AppendList(list); }
break; }
default:
AppendString(); private void AppendDictionary(Dictionary<String, Object> objects) {
break; foreach(KeyValuePair<String, Object> kvp in objects) {
} if(kvp.Value == null) {
} continue;
}
private void AppendDictionary(Dictionary<string, object> objects)
{ Boolean writeOutput = false;
foreach (var kvp in objects)
{ switch(kvp.Value) {
if (kvp.Value == null) continue; case Dictionary<String, Object> valueDictionary:
if(valueDictionary.Count > 0) {
var writeOutput = false; writeOutput = true;
_ = this._builder
switch (kvp.Value) .Append($"{this._indentStr}{kvp.Key,-16}: object")
{ .AppendLine();
case Dictionary<string, object> valueDictionary: }
if (valueDictionary.Count > 0)
{ break;
writeOutput = true; case List<Object> valueList:
_builder if(valueList.Count > 0) {
.Append($"{_indentStr}{kvp.Key,-16}: object") writeOutput = true;
.AppendLine(); _ = this._builder
} .Append($"{this._indentStr}{kvp.Key,-16}: array[{valueList.Count}]")
.AppendLine();
break; }
case List<object> valueList:
if (valueList.Count > 0) break;
{ default:
writeOutput = true; writeOutput = true;
_builder _ = this._builder.Append($"{this._indentStr}{kvp.Key,-16}: ");
.Append($"{_indentStr}{kvp.Key,-16}: array[{valueList.Count}]") break;
.AppendLine(); }
}
if(writeOutput) {
break; _ = this._builder.AppendLine(new HumanizeJson(kvp.Value, this._indent + 1).GetResult());
default: }
writeOutput = true; }
_builder.Append($"{_indentStr}{kvp.Key,-16}: "); }
break;
} private void AppendList(List<Object> objects) {
Int32 index = 0;
if (writeOutput) foreach(Object value in objects) {
_builder.AppendLine(new HumanizeJson(kvp.Value, _indent + 1).GetResult()); Boolean writeOutput = false;
}
} switch(value) {
case Dictionary<String, Object> valueDictionary:
private void AppendList(List<object> objects) if(valueDictionary.Count > 0) {
{ writeOutput = true;
var index = 0; _ = this._builder
foreach (var value in objects) .Append($"{this._indentStr}[{index}]: object")
{ .AppendLine();
var writeOutput = false; }
switch (value) break;
{ case List<Object> valueList:
case Dictionary<string, object> valueDictionary: if(valueList.Count > 0) {
if (valueDictionary.Count > 0) writeOutput = true;
{ _ = this._builder
writeOutput = true; .Append($"{this._indentStr}[{index}]: array[{valueList.Count}]")
_builder .AppendLine();
.Append($"{_indentStr}[{index}]: object") }
.AppendLine();
} break;
default:
break; writeOutput = true;
case List<object> valueList: _ = this._builder.Append($"{this._indentStr}[{index}]: ");
if (valueList.Count > 0) break;
{ }
writeOutput = true;
_builder index++;
.Append($"{_indentStr}[{index}]: array[{valueList.Count}]")
.AppendLine(); if(writeOutput) {
} _ = this._builder.AppendLine(new HumanizeJson(value, this._indent + 1).GetResult());
}
break; }
default: }
writeOutput = true;
_builder.Append($"{_indentStr}[{index}]: "); private void AppendString() {
break; String stringValue = this._obj.ToString();
}
if(stringValue.Length + this._indentStr.Length > 96 || stringValue.IndexOf('\r') >= 0 ||
index++; stringValue.IndexOf('\n') >= 0) {
_ = this._builder.AppendLine();
if (writeOutput) IEnumerable<String> stringLines = stringValue.ToLines().Select(l => l.Trim());
_builder.AppendLine(new HumanizeJson(value, _indent + 1).GetResult());
} foreach(String line in stringLines) {
} _ = this._builder.AppendLine($"{this._indentStr}{line}");
}
private void AppendString() } else {
{ _ = this._builder.Append($"{stringValue}");
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}");
}
}
}
} }

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; namespace Unosquare.Swan.Formatters {
/// <summary>
/// <summary> /// A very simple, light-weight JSON library written by Mario
/// A very simple, light-weight JSON library written by Mario /// to teach Geo how things are done
/// to teach Geo how things are done ///
/// /// This is an useful helper for small tasks but it doesn't represent a full-featured
/// This is an useful helper for small tasks but it doesn't represent a full-featured /// serializer such as the beloved Json.NET.
/// serializer such as the beloved Json.NET. /// </summary>
/// </summary> public static partial class Json {
public static partial class Json private class Converter {
{ private static readonly ConcurrentDictionary<MemberInfo, String> MemberInfoNameCache =
private class Converter new ConcurrentDictionary<MemberInfo, String>();
{
private static readonly ConcurrentDictionary<MemberInfo, string> MemberInfoNameCache = private static readonly ConcurrentDictionary<Type, Type> ListAddMethodCache = new ConcurrentDictionary<Type, Type>();
new ConcurrentDictionary<MemberInfo, string>();
private readonly Object _target;
private static readonly ConcurrentDictionary<Type, Type> ListAddMethodCache = new ConcurrentDictionary<Type, Type>(); private readonly Type _targetType;
private readonly Boolean _includeNonPublic;
private readonly object _target;
private readonly Type _targetType; private Converter(
private readonly bool _includeNonPublic; Object source,
Type targetType,
private Converter( ref Object targetInstance,
object source, Boolean includeNonPublic) {
Type targetType, this._targetType = targetInstance != null ? targetInstance.GetType() : targetType;
ref object targetInstance, this._includeNonPublic = includeNonPublic;
bool includeNonPublic)
{ if(source == null) {
_targetType = targetInstance != null ? targetInstance.GetType() : targetType; return;
_includeNonPublic = includeNonPublic; }
if (source == null) Type sourceType = source.GetType();
{
return; if(this._targetType == null || this._targetType == typeof(Object)) {
} this._targetType = sourceType;
}
var sourceType = source.GetType();
if(sourceType == this._targetType) {
if (_targetType == null || _targetType == typeof(object)) _targetType = sourceType; this._target = source;
if (sourceType == _targetType) return;
{ }
_target = source;
return; if(!this.TrySetInstance(targetInstance, source, ref this._target)) {
} return;
}
if (!TrySetInstance(targetInstance, source, ref _target))
return; this.ResolveObject(source, ref this._target);
}
ResolveObject(source, ref _target);
} /// <summary>
/// Converts a json deserialized object (simple type, dictionary or list) to a new instance of the specified target type.
/// <summary> /// </summary>
/// Converts a json deserialized object (simple type, dictionary or list) to a new instance of the specified target type. /// <param name="source">The source.</param>
/// </summary> /// <param name="targetType">Type of the target.</param>
/// <param name="source">The source.</param> /// <param name="includeNonPublic">if set to <c>true</c> [include non public].</param>
/// <param name="targetType">Type of the target.</param> /// <returns>The target object.</returns>
/// <param name="includeNonPublic">if set to <c>true</c> [include non public].</param> internal static Object FromJsonResult(Object source,
/// <returns>The target object.</returns> Type targetType,
internal static object FromJsonResult(object source, Boolean includeNonPublic) {
Type targetType, Object nullRef = null;
bool includeNonPublic) return new Converter(source, targetType, ref nullRef, includeNonPublic).GetResult();
{ }
object nullRef = null;
return new Converter(source, targetType, ref nullRef, includeNonPublic).GetResult(); private static Object FromJsonResult(Object source,
} Type targetType,
ref Object targetInstance,
private static object FromJsonResult(object source, Boolean includeNonPublic) => new Converter(source, targetType, ref targetInstance, includeNonPublic).GetResult();
Type targetType,
ref object targetInstance, private static Type GetAddMethodParameterType(Type targetType)
bool includeNonPublic) => ListAddMethodCache.GetOrAdd(targetType,
{ x => x.GetMethods()
return new Converter(source, targetType, ref targetInstance, includeNonPublic).GetResult(); .FirstOrDefault(
} m => m.Name.Equals(AddMethodName) && m.IsPublic && m.GetParameters().Length == 1)?
.GetParameters()[0]
private static Type GetAddMethodParameterType(Type targetType) .ParameterType);
=> ListAddMethodCache.GetOrAdd(targetType,
x => x.GetMethods() private static void GetByteArray(String sourceString, ref Object target) {
.FirstOrDefault( try {
m => m.Name.Equals(AddMethodName) && m.IsPublic && m.GetParameters().Length == 1)? target = Convert.FromBase64String(sourceString);
.GetParameters()[0] } // Try conversion from Base 64
.ParameterType); catch {
target = Encoding.UTF8.GetBytes(sourceString);
private static void GetByteArray(string sourceString, ref object target) } // Get the string bytes in UTF8
{ }
try
{ private static Object GetSourcePropertyValue(IDictionary<String, Object> sourceProperties,
target = Convert.FromBase64String(sourceString); MemberInfo targetProperty) {
} // Try conversion from Base 64 String targetPropertyName = MemberInfoNameCache.GetOrAdd(
catch targetProperty,
{ x => Runtime.AttributeCache.RetrieveOne<JsonPropertyAttribute>(x)?.PropertyName ?? x.Name);
target = Encoding.UTF8.GetBytes(sourceString);
} // Get the string bytes in UTF8 return sourceProperties.GetValueOrDefault(targetPropertyName);
} }
private static object GetSourcePropertyValue(IDictionary<string, object> sourceProperties, private Boolean TrySetInstance(Object targetInstance, Object source, ref Object target) {
MemberInfo targetProperty) if(targetInstance == null) {
{ // Try to create a default instance
var targetPropertyName = MemberInfoNameCache.GetOrAdd( try {
targetProperty, source.CreateTarget(this._targetType, this._includeNonPublic, ref target);
x => Runtime.AttributeCache.RetrieveOne<JsonPropertyAttribute>(x)?.PropertyName ?? x.Name); } catch {
return false;
return sourceProperties.GetValueOrDefault(targetPropertyName); }
} } else {
target = targetInstance;
private bool TrySetInstance(object targetInstance, object source, ref object target) }
{
if (targetInstance == null) return true;
{ }
// Try to create a default instance
try private Object GetResult() => this._target ?? this._targetType.GetDefault();
{
source.CreateTarget(_targetType, _includeNonPublic, ref target); private void ResolveObject(Object source, ref Object target) {
} switch(source) {
catch // Case 0: Special Cases Handling (Source and Target are of specific convertible types)
{ // Case 0.1: Source is string, Target is byte[]
return false; case String sourceString when this._targetType == typeof(Byte[]):
} GetByteArray(sourceString, ref target);
} break;
else
{ // Case 1.1: Source is Dictionary, Target is IDictionary
target = targetInstance; case Dictionary<String, Object> sourceProperties when target is IDictionary targetDictionary:
} this.PopulateDictionary(sourceProperties, targetDictionary);
break;
return true;
} // Case 1.2: Source is Dictionary, Target is not IDictionary (i.e. it is a complex type)
case Dictionary<String, Object> sourceProperties:
private object GetResult() => _target ?? _targetType.GetDefault(); this.PopulateObject(sourceProperties);
break;
private void ResolveObject(object source, ref object target)
{ // Case 2.1: Source is List, Target is Array
switch (source) case List<Object> sourceList when target is Array targetArray:
{ this.PopulateArray(sourceList, targetArray);
// Case 0: Special Cases Handling (Source and Target are of specific convertible types) break;
// Case 0.1: Source is string, Target is byte[]
case string sourceString when _targetType == typeof(byte[]): // Case 2.2: Source is List, Target is IList
GetByteArray(sourceString, ref target); case List<Object> sourceList when target is IList targetList:
break; this.PopulateIList(sourceList, targetList);
break;
// Case 1.1: Source is Dictionary, Target is IDictionary
case Dictionary<string, object> sourceProperties when target is IDictionary targetDictionary: // Case 3: Source is a simple type; Attempt conversion
PopulateDictionary(sourceProperties, targetDictionary); default:
break; String sourceStringValue = source.ToStringInvariant();
// Case 1.2: Source is Dictionary, Target is not IDictionary (i.e. it is a complex type) // Handle basic types or enumerations if not
case Dictionary<string, object> sourceProperties: if(!this._targetType.TryParseBasicType(sourceStringValue, out target)) {
PopulateObject(sourceProperties); this.GetEnumValue(sourceStringValue, ref target);
break; }
// Case 2.1: Source is List, Target is Array break;
case List<object> sourceList when target is Array targetArray: }
PopulateArray(sourceList, targetArray); }
break;
private void PopulateIList(IList<Object> objects, IList list) {
// Case 2.2: Source is List, Target is IList Type parameterType = GetAddMethodParameterType(this._targetType);
case List<object> sourceList when target is IList targetList: if(parameterType == null) {
PopulateIList(sourceList, targetList); return;
break; }
// Case 3: Source is a simple type; Attempt conversion foreach(Object item in objects) {
default: try {
var sourceStringValue = source.ToStringInvariant(); _ = list.Add(FromJsonResult(
item,
// Handle basic types or enumerations if not parameterType,
if (!_targetType.TryParseBasicType(sourceStringValue, out target)) this._includeNonPublic));
GetEnumValue(sourceStringValue, ref target); } catch {
// ignored
break; }
} }
} }
private void PopulateIList(IList<object> objects, IList list) private void PopulateArray(IList<Object> objects, Array array) {
{ Type elementType = this._targetType.GetElementType();
var parameterType = GetAddMethodParameterType(_targetType);
if (parameterType == null) return; for(Int32 i = 0; i < objects.Count; i++) {
try {
foreach (var item in objects) Object targetItem = FromJsonResult(
{ objects[i],
try elementType,
{ this._includeNonPublic);
list.Add(FromJsonResult( array.SetValue(targetItem, i);
item, } catch {
parameterType, // ignored
_includeNonPublic)); }
} }
catch }
{
// ignored 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 void PopulateArray(IList<object> objects, Array array)
{ if(enumType == null) {
var elementType = _targetType.GetElementType(); return;
}
for (var i = 0; i < objects.Count; i++)
{ try {
try target = Enum.Parse(enumType, sourceStringValue);
{ } catch {
var targetItem = FromJsonResult( // ignored
objects[i], }
elementType, }
_includeNonPublic);
array.SetValue(targetItem, i); private void PopulateDictionary(IDictionary<String, Object> sourceProperties, IDictionary targetDictionary) {
} // find the add method of the target dictionary
catch MethodInfo addMethod = this._targetType.GetMethods()
{ .FirstOrDefault(
// ignored 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;
private void GetEnumValue(string sourceStringValue, ref object target) }
{
var enumType = Nullable.GetUnderlyingType(_targetType); ParameterInfo[] addMethodParameters = addMethod.GetParameters();
if (enumType == null && _targetType.GetTypeInfo().IsEnum) enumType = _targetType; if(addMethodParameters[0].ParameterType != typeof(String)) {
if (enumType == null) return; return;
}
try
{ // Retrieve the target entry type
target = Enum.Parse(enumType, sourceStringValue); Type targetEntryType = addMethodParameters[1].ParameterType;
}
catch // Add the items to the target dictionary
{ foreach(KeyValuePair<String, Object> sourceProperty in sourceProperties) {
// ignored try {
} Object targetEntryValue = FromJsonResult(
} sourceProperty.Value,
targetEntryType,
private void PopulateDictionary(IDictionary<string, object> sourceProperties, IDictionary targetDictionary) this._includeNonPublic);
{ targetDictionary.Add(sourceProperty.Key, targetEntryValue);
// find the add method of the target dictionary } catch {
var addMethod = _targetType.GetMethods() // ignored
.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; private void PopulateObject(IDictionary<String, Object> sourceProperties) {
var addMethodParameters = addMethod.GetParameters(); if(this._targetType.IsValueType()) {
if (addMethodParameters[0].ParameterType != typeof(string)) return; this.PopulateFields(sourceProperties);
}
// Retrieve the target entry type
var targetEntryType = addMethodParameters[1].ParameterType; this.PopulateProperties(sourceProperties);
}
// Add the items to the target dictionary
foreach (var sourceProperty in sourceProperties) private void PopulateProperties(IDictionary<String, Object> sourceProperties) {
{ IEnumerable<PropertyInfo> properties = PropertyTypeCache.RetrieveFilteredProperties(this._targetType, false, p => p.CanWrite);
try
{ foreach(PropertyInfo property in properties) {
var targetEntryValue = FromJsonResult( Object sourcePropertyValue = GetSourcePropertyValue(sourceProperties, property);
sourceProperty.Value, if(sourcePropertyValue == null) {
targetEntryType, continue;
_includeNonPublic); }
targetDictionary.Add(sourceProperty.Key, targetEntryValue);
} try {
catch Object currentPropertyValue = !property.PropertyType.IsArray
{ ? property.GetCacheGetMethod(this._includeNonPublic)(this._target)
// ignored : null;
}
} Object targetPropertyValue = FromJsonResult(
} sourcePropertyValue,
property.PropertyType,
private void PopulateObject(IDictionary<string, object> sourceProperties) ref currentPropertyValue,
{ this._includeNonPublic);
if (_targetType.IsValueType())
{ property.GetCacheSetMethod(this._includeNonPublic)(this._target, new[] { targetPropertyValue });
PopulateFields(sourceProperties); } catch {
} // ignored
}
PopulateProperties(sourceProperties); }
} }
private void PopulateProperties(IDictionary<string, object> sourceProperties) private void PopulateFields(IDictionary<String, Object> sourceProperties) {
{ foreach(FieldInfo field in FieldTypeCache.RetrieveAllFields(this._targetType)) {
var properties = PropertyTypeCache.RetrieveFilteredProperties(_targetType, false, p => p.CanWrite); Object sourcePropertyValue = GetSourcePropertyValue(sourceProperties, field);
if(sourcePropertyValue == null) {
foreach (var property in properties) continue;
{ }
var sourcePropertyValue = GetSourcePropertyValue(sourceProperties, property);
if (sourcePropertyValue == null) continue; Object targetPropertyValue = FromJsonResult(
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. private readonly Object _result;
/// </summary> private readonly Dictionary<String, Object> _resultObject;
private class Deserializer private readonly List<Object> _resultArray;
{
#region State Variables private readonly ReadState _state = ReadState.WaitingForRootOpen;
private readonly String _currentFieldName;
private readonly object _result; private readonly String _json;
private readonly Dictionary<string, object> _resultObject;
private readonly List<object> _resultArray; private Int32 _index;
private readonly ReadState _state = ReadState.WaitingForRootOpen; #endregion
private readonly string _currentFieldName;
private readonly string _json; private Deserializer(String json, Int32 startIndex) {
this._json = json;
private int _index;
for(this._index = startIndex; this._index < this._json.Length; this._index++) {
#endregion #region Wait for { or [
private Deserializer(string json, int startIndex) if(this._state == ReadState.WaitingForRootOpen) {
{ if(Char.IsWhiteSpace(this._json, this._index)) {
_json = json; continue;
}
for (_index = startIndex; _index < _json.Length; _index++)
{ if(this._json[this._index] == OpenObjectChar) {
#region Wait for { or [ this._resultObject = new Dictionary<String, Object>();
this._state = ReadState.WaitingForField;
if (_state == ReadState.WaitingForRootOpen) continue;
{ }
if (char.IsWhiteSpace(_json, _index)) continue;
if(this._json[this._index] == OpenArrayChar) {
if (_json[_index] == OpenObjectChar) this._resultArray = new List<Object>();
{ this._state = ReadState.WaitingForValue;
_resultObject = new Dictionary<string, object>(); continue;
_state = ReadState.WaitingForField; }
continue;
} throw this.CreateParserException($"'{OpenObjectChar}' or '{OpenArrayChar}'");
}
if (_json[_index] == OpenArrayChar)
{ #endregion
_resultArray = new List<object>();
_state = ReadState.WaitingForValue; #region Wait for opening field " (only applies for object results)
continue;
} if(this._state == ReadState.WaitingForField) {
if(Char.IsWhiteSpace(this._json, this._index)) {
throw CreateParserException($"'{OpenObjectChar}' or '{OpenArrayChar}'"); continue;
} }
#endregion // Handle empty arrays and empty objects
if(this._resultObject != null && this._json[this._index] == CloseObjectChar
#region Wait for opening field " (only applies for object results) || this._resultArray != null && this._json[this._index] == CloseArrayChar) {
this._result = this._resultObject ?? this._resultArray as Object;
if (_state == ReadState.WaitingForField) return;
{ }
if (char.IsWhiteSpace(_json, _index)) continue;
if(this._json[this._index] != StringQuotedChar) {
// Handle empty arrays and empty objects throw this.CreateParserException($"'{StringQuotedChar}'");
if ((_resultObject != null && _json[_index] == CloseObjectChar) }
|| (_resultArray != null && _json[_index] == CloseArrayChar))
{ Int32 charCount = this.GetFieldNameCount();
_result = _resultObject ?? _resultArray as object;
return; this._currentFieldName = Unescape(this._json.SliceLength(this._index + 1, charCount));
} this._index += charCount + 1;
this._state = ReadState.WaitingForColon;
if (_json[_index] != StringQuotedChar) continue;
throw CreateParserException($"'{StringQuotedChar}'"); }
var charCount = GetFieldNameCount(); #endregion
_currentFieldName = Unescape(_json.SliceLength(_index + 1, charCount)); #region Wait for field-value separator : (only applies for object results
_index += charCount + 1;
_state = ReadState.WaitingForColon; if(this._state == ReadState.WaitingForColon) {
continue; if(Char.IsWhiteSpace(this._json, this._index)) {
} continue;
}
#endregion
if(this._json[this._index] != ValueSeparatorChar) {
#region Wait for field-value separator : (only applies for object results throw this.CreateParserException($"'{ValueSeparatorChar}'");
}
if (_state == ReadState.WaitingForColon)
{ this._state = ReadState.WaitingForValue;
if (char.IsWhiteSpace(_json, _index)) continue; continue;
}
if (_json[_index] != ValueSeparatorChar)
throw CreateParserException($"'{ValueSeparatorChar}'"); #endregion
_state = ReadState.WaitingForValue; #region Wait for and Parse the value
continue;
} if(this._state == ReadState.WaitingForValue) {
if(Char.IsWhiteSpace(this._json, this._index)) {
#endregion continue;
}
#region Wait for and Parse the value
// Handle empty arrays and empty objects
if (_state == ReadState.WaitingForValue) if(this._resultObject != null && this._json[this._index] == CloseObjectChar
{ || this._resultArray != null && this._json[this._index] == CloseArrayChar) {
if (char.IsWhiteSpace(_json, _index)) continue; this._result = this._resultObject ?? this._resultArray as Object;
return;
// Handle empty arrays and empty objects }
if ((_resultObject != null && _json[_index] == CloseObjectChar)
|| (_resultArray != null && _json[_index] == CloseArrayChar)) // determine the value based on what it starts with
{ switch(this._json[this._index]) {
_result = _resultObject ?? _resultArray as object; case StringQuotedChar: // expect a string
return; this.ExtractStringQuoted();
} break;
// determine the value based on what it starts with case OpenObjectChar: // expect object
switch (_json[_index]) case OpenArrayChar: // expect array
{ this.ExtractObject();
case StringQuotedChar: // expect a string break;
ExtractStringQuoted();
break; case 't': // expect true
this.ExtractConstant(TrueLiteral, true);
case OpenObjectChar: // expect object break;
case OpenArrayChar: // expect array
ExtractObject(); case 'f': // expect false
break; this.ExtractConstant(FalseLiteral, false);
break;
case 't': // expect true
ExtractConstant(TrueLiteral, true); case 'n': // expect null
break; this.ExtractConstant(NullLiteral, null);
break;
case 'f': // expect false
ExtractConstant(FalseLiteral, false); default: // expect number
break; this.ExtractNumber();
break;
case 'n': // expect null }
ExtractConstant(NullLiteral, null);
break; this._currentFieldName = null;
this._state = ReadState.WaitingForNextOrRootClose;
default: // expect number continue;
ExtractNumber(); }
break;
} #endregion
_currentFieldName = null; #region Wait for closing ], } or an additional field or value ,
_state = ReadState.WaitingForNextOrRootClose;
continue; if(this._state != ReadState.WaitingForNextOrRootClose) {
} continue;
}
#endregion
if(Char.IsWhiteSpace(this._json, this._index)) {
#region Wait for closing ], } or an additional field or value , continue;
}
if (_state != ReadState.WaitingForNextOrRootClose) continue;
if(this._json[this._index] == FieldSeparatorChar) {
if (char.IsWhiteSpace(_json, _index)) continue; if(this._resultObject != null) {
this._state = ReadState.WaitingForField;
if (_json[_index] == FieldSeparatorChar) this._currentFieldName = null;
{ continue;
if (_resultObject != null) }
{
_state = ReadState.WaitingForField; this._state = ReadState.WaitingForValue;
_currentFieldName = null; continue;
continue; }
}
if(this._resultObject != null && this._json[this._index] == CloseObjectChar ||
_state = ReadState.WaitingForValue; this._resultArray != null && this._json[this._index] == CloseArrayChar) {
continue; this._result = this._resultObject ?? this._resultArray as Object;
} return;
}
if ((_resultObject != null && _json[_index] == CloseObjectChar) ||
(_resultArray != null && _json[_index] == CloseArrayChar)) throw this.CreateParserException($"'{FieldSeparatorChar}' '{CloseObjectChar}' or '{CloseArrayChar}'");
{
_result = _resultObject ?? _resultArray as object; #endregion
return; }
} }
throw CreateParserException($"'{FieldSeparatorChar}' '{CloseObjectChar}' or '{CloseArrayChar}'"); internal static Object DeserializeInternal(String json) => new Deserializer(json, 0)._result;
#endregion private static String Unescape(String str) {
} // check if we need to unescape at all
} if(str.IndexOf(StringEscapeChar) < 0) {
return str;
internal static object DeserializeInternal(string json) => new Deserializer(json, 0)._result; }
private static string Unescape(string str) StringBuilder builder = new StringBuilder(str.Length);
{ for(Int32 i = 0; i < str.Length; i++) {
// check if we need to unescape at all if(str[i] != StringEscapeChar) {
if (str.IndexOf(StringEscapeChar) < 0) _ = builder.Append(str[i]);
return str; continue;
}
var builder = new StringBuilder(str.Length);
for (var i = 0; i < str.Length; i++) if(i + 1 > str.Length - 1) {
{ break;
if (str[i] != StringEscapeChar) }
{
builder.Append(str[i]); // escape sequence begins here
continue; switch(str[i + 1]) {
} case 'u':
i = ExtractEscapeSequence(str, i, builder);
if (i + 1 > str.Length - 1) break;
break; case 'b':
_ = builder.Append('\b');
// escape sequence begins here i += 1;
switch (str[i + 1]) break;
{ case 't':
case 'u': _ = builder.Append('\t');
i = ExtractEscapeSequence(str, i, builder); i += 1;
break; break;
case 'b': case 'n':
builder.Append('\b'); _ = builder.Append('\n');
i += 1; i += 1;
break; break;
case 't': case 'f':
builder.Append('\t'); _ = builder.Append('\f');
i += 1; i += 1;
break; break;
case 'n': case 'r':
builder.Append('\n'); _ = builder.Append('\r');
i += 1; i += 1;
break; break;
case 'f': default:
builder.Append('\f'); _ = builder.Append(str[i + 1]);
i += 1; i += 1;
break; break;
case 'r': }
builder.Append('\r'); }
i += 1;
break; return builder.ToString();
default: }
builder.Append(str[i + 1]);
i += 1; private static Int32 ExtractEscapeSequence(String str, Int32 i, StringBuilder builder) {
break; Int32 startIndex = i + 2;
} Int32 endIndex = i + 5;
} if(endIndex > str.Length - 1) {
_ = builder.Append(str[i + 1]);
return builder.ToString(); i += 1;
} return i;
}
private static int ExtractEscapeSequence(string str, int i, StringBuilder builder)
{ Byte[] hexCode = str.Slice(startIndex, endIndex).ConvertHexadecimalToBytes();
var startIndex = i + 2; _ = builder.Append(Encoding.BigEndianUnicode.GetChars(hexCode));
var endIndex = i + 5; i += 5;
if (endIndex > str.Length - 1) return i;
{ }
builder.Append(str[i + 1]);
i += 1; private Int32 GetFieldNameCount() {
return i; Int32 charCount = 0;
} for(Int32 j = this._index + 1; j < this._json.Length; j++) {
if(this._json[j] == StringQuotedChar && this._json[j - 1] != StringEscapeChar) {
var hexCode = str.Slice(startIndex, endIndex).ConvertHexadecimalToBytes(); break;
builder.Append(Encoding.BigEndianUnicode.GetChars(hexCode)); }
i += 5;
return i; charCount++;
} }
private int GetFieldNameCount() return charCount;
{ }
var charCount = 0;
for (var j = _index + 1; j < _json.Length; j++) private void ExtractObject() {
{ // Extract and set the value
if (_json[j] == StringQuotedChar && _json[j - 1] != StringEscapeChar) Deserializer deserializer = new Deserializer(this._json, this._index);
break;
if(this._currentFieldName != null) {
charCount++; this._resultObject[this._currentFieldName] = deserializer._result;
} } else {
this._resultArray.Add(deserializer._result);
return charCount; }
}
this._index = deserializer._index;
private void ExtractObject() }
{
// Extract and set the value private void ExtractNumber() {
var deserializer = new Deserializer(_json, _index); Int32 charCount = 0;
for(Int32 j = this._index; j < this._json.Length; j++) {
if (_currentFieldName != null) if(Char.IsWhiteSpace(this._json[j]) || this._json[j] == FieldSeparatorChar
_resultObject[_currentFieldName] = deserializer._result; || this._resultObject != null && this._json[j] == CloseObjectChar
else || this._resultArray != null && this._json[j] == CloseArrayChar) {
_resultArray.Add(deserializer._result); break;
}
_index = deserializer._index;
} charCount++;
}
private void ExtractNumber()
{ // Extract and set the value
var charCount = 0; String stringValue = this._json.SliceLength(this._index, charCount);
for (var j = _index; j < _json.Length; j++)
{ if(Decimal.TryParse(stringValue, out Decimal value) == false) {
if (char.IsWhiteSpace(_json[j]) || _json[j] == FieldSeparatorChar throw this.CreateParserException("[number]");
|| (_resultObject != null && _json[j] == CloseObjectChar) }
|| (_resultArray != null && _json[j] == CloseArrayChar))
break; if(this._currentFieldName != null) {
this._resultObject[this._currentFieldName] = value;
charCount++; } else {
} this._resultArray.Add(value);
}
// Extract and set the value
var stringValue = _json.SliceLength(_index, charCount); this._index += charCount - 1;
}
if (decimal.TryParse(stringValue, out var value) == false)
throw CreateParserException("[number]"); private void ExtractConstant(String boolValue, Boolean? value) {
if(!this._json.SliceLength(this._index, boolValue.Length).Equals(boolValue)) {
if (_currentFieldName != null) throw this.CreateParserException($"'{ValueSeparatorChar}'");
_resultObject[_currentFieldName] = value; }
else
_resultArray.Add(value); // Extract and set the value
if(this._currentFieldName != null) {
_index += charCount - 1; this._resultObject[this._currentFieldName] = value;
} } else {
this._resultArray.Add(value);
private void ExtractConstant(string boolValue, bool? value) }
{
if (!_json.SliceLength(_index, boolValue.Length).Equals(boolValue)) this._index += boolValue.Length - 1;
throw CreateParserException($"'{ValueSeparatorChar}'"); }
// Extract and set the value private void ExtractStringQuoted() {
if (_currentFieldName != null) Int32 charCount = 0;
_resultObject[_currentFieldName] = value; Boolean escapeCharFound = false;
else for(Int32 j = this._index + 1; j < this._json.Length; j++) {
_resultArray.Add(value); if(this._json[j] == StringQuotedChar && !escapeCharFound) {
break;
_index += boolValue.Length - 1; }
}
escapeCharFound = this._json[j] == StringEscapeChar && !escapeCharFound;
private void ExtractStringQuoted() charCount++;
{ }
var charCount = 0;
var escapeCharFound = false; // Extract and set the value
for (var j = _index + 1; j < _json.Length; j++) String value = Unescape(this._json.SliceLength(this._index + 1, charCount));
{ if(this._currentFieldName != null) {
if (_json[j] == StringQuotedChar && !escapeCharFound) this._resultObject[this._currentFieldName] = value;
break; } else {
this._resultArray.Add(value);
escapeCharFound = _json[j] == StringEscapeChar && !escapeCharFound; }
charCount++;
} this._index += charCount + 1;
}
// Extract and set the value
var value = Unescape(_json.SliceLength(_index + 1, charCount)); private FormatException CreateParserException(String expected) {
if (_currentFieldName != null) Tuple<Int32, Int32> textPosition = this._json.TextPositionAt(this._index);
_resultObject[_currentFieldName] = value; return new FormatException(
else $"Parser error (Line {textPosition.Item1}, Col {textPosition.Item2}, State {this._state}): Expected {expected} but got '{this._json[this._index]}'.");
_resultArray.Add(value); }
_index += charCount + 1; /// <summary>
} /// Defines the different JSON read states.
/// </summary>
private FormatException CreateParserException(string expected) private enum ReadState {
{ WaitingForRootOpen,
var textPosition = _json.TextPositionAt(_index); WaitingForField,
return new FormatException( WaitingForColon,
$"Parser error (Line {textPosition.Item1}, Col {textPosition.Item2}, State {_state}): Expected {expected} but got '{_json[_index]}'."); WaitingForValue,
} WaitingForNextOrRootClose,
}
/// <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. private static readonly Dictionary<Int32, String> IndentStrings = new Dictionary<Int32, String>();
/// </summary>
private class Serializer private readonly SerializerOptions _options;
{ private readonly String _result;
#region Private Declarations private readonly StringBuilder _builder;
private readonly String _lastCommaSearch;
private static readonly Dictionary<int, string> IndentStrings = new Dictionary<int, string>();
#endregion
private readonly SerializerOptions _options;
private readonly string _result; #region Constructors
private readonly StringBuilder _builder;
private readonly string _lastCommaSearch; /// <summary>
/// Initializes a new instance of the <see cref="Serializer" /> class.
#endregion /// </summary>
/// <param name="obj">The object.</param>
#region Constructors /// <param name="depth">The depth.</param>
/// <param name="options">The options.</param>
/// <summary> private Serializer(Object obj, Int32 depth, SerializerOptions options) {
/// Initializes a new instance of the <see cref="Serializer" /> class. if(depth > 20) {
/// </summary> throw new InvalidOperationException(
/// <param name="obj">The object.</param> "The max depth (20) has been reached. Serializer can not continue.");
/// <param name="depth">The depth.</param> }
/// <param name="options">The options.</param>
private Serializer(object obj, int depth, SerializerOptions options) // Basic Type Handling (nulls, strings, number, date and bool)
{ this._result = ResolveBasicType(obj);
if (depth > 20)
{ if(String.IsNullOrWhiteSpace(this._result) == false) {
throw new InvalidOperationException( return;
"The max depth (20) has been reached. Serializer can not continue."); }
}
this._options = options;
// Basic Type Handling (nulls, strings, number, date and bool) this._lastCommaSearch = FieldSeparatorChar + (this._options.Format ? Environment.NewLine : String.Empty);
_result = ResolveBasicType(obj);
// Handle circular references correctly and avoid them
if (string.IsNullOrWhiteSpace(_result) == false) if(options.IsObjectPresent(obj)) {
return; this._result = $"{{ \"$circref\": \"{Escape(obj.GetHashCode().ToStringInvariant(), false)}\" }}";
return;
_options = options; }
_lastCommaSearch = FieldSeparatorChar + (_options.Format ? Environment.NewLine : string.Empty);
// At this point, we will need to construct the object with a StringBuilder.
// Handle circular references correctly and avoid them this._builder = new StringBuilder();
if (options.IsObjectPresent(obj))
{ switch(obj) {
_result = $"{{ \"$circref\": \"{Escape(obj.GetHashCode().ToStringInvariant(), false)}\" }}"; case IDictionary itemsZero when itemsZero.Count == 0:
return; this._result = EmptyObjectLiteral;
} break;
case IDictionary items:
// At this point, we will need to construct the object with a StringBuilder. this._result = this.ResolveDictionary(items, depth);
_builder = new StringBuilder(); break;
case IEnumerable enumerableZero when !enumerableZero.Cast<Object>().Any():
switch (obj) this._result = EmptyArrayLiteral;
{ break;
case IDictionary itemsZero when itemsZero.Count == 0: case IEnumerable enumerableBytes when enumerableBytes is Byte[] bytes:
_result = EmptyObjectLiteral; this._result = Serialize(bytes.ToBase64(), depth, this._options);
break; break;
case IDictionary items: case IEnumerable enumerable:
_result = ResolveDictionary(items, depth); this._result = this.ResolveEnumerable(enumerable, depth);
break; break;
case IEnumerable enumerableZero when !enumerableZero.Cast<object>().Any(): default:
_result = EmptyArrayLiteral; this._result = this.ResolveObject(obj, depth);
break; break;
case IEnumerable enumerableBytes when enumerableBytes is byte[] bytes: }
_result = Serialize(bytes.ToBase64(), depth, _options); }
break;
case IEnumerable enumerable: internal static String Serialize(Object obj, Int32 depth, SerializerOptions options) => new Serializer(obj, depth, options)._result;
_result = ResolveEnumerable(enumerable, depth);
break; #endregion
default:
_result = ResolveObject(obj, depth); #region Helper Methods
break;
} private static String ResolveBasicType(Object obj) {
} switch(obj) {
case null:
internal static string Serialize(object obj, int depth, SerializerOptions options) return NullLiteral;
{ case String s:
return new Serializer(obj, depth, options)._result; return Escape(s, true);
} case Boolean b:
return b ? TrueLiteral : FalseLiteral;
#endregion case Type _:
case Assembly _:
#region Helper Methods case MethodInfo _:
case PropertyInfo _:
private static string ResolveBasicType(object obj) case EventInfo _:
{ return Escape(obj.ToString(), true);
switch (obj) case DateTime d:
{ return $"{StringQuotedChar}{d:s}{StringQuotedChar}";
case null: default:
return NullLiteral; Type targetType = obj.GetType();
case string s:
return Escape(s, true); if(!Definitions.BasicTypesInfo.ContainsKey(targetType)) {
case bool b: return String.Empty;
return b ? TrueLiteral : FalseLiteral; }
case Type _:
case Assembly _: String escapedValue = Escape(Definitions.BasicTypesInfo[targetType].ToStringInvariant(obj), false);
case MethodInfo _:
case PropertyInfo _: return Decimal.TryParse(escapedValue, out _)
case EventInfo _: ? $"{escapedValue}"
return Escape(obj.ToString(), true); : $"{StringQuotedChar}{escapedValue}{StringQuotedChar}";
case DateTime d: }
return $"{StringQuotedChar}{d:s}{StringQuotedChar}"; }
default:
var targetType = obj.GetType(); private static Boolean IsNonEmptyJsonArrayOrObject(String serialized) {
if(serialized.Equals(EmptyObjectLiteral) || serialized.Equals(EmptyArrayLiteral)) {
if (!Definitions.BasicTypesInfo.ContainsKey(targetType)) return false;
return string.Empty; }
var escapedValue = Escape(Definitions.BasicTypesInfo[targetType].ToStringInvariant(obj), false); // find the first position the character is not a space
return serialized.Where(c => c != ' ').Select(c => c == OpenObjectChar || c == OpenArrayChar).FirstOrDefault();
return decimal.TryParse(escapedValue, out _) }
? $"{escapedValue}"
: $"{StringQuotedChar}{escapedValue}{StringQuotedChar}"; private static String Escape(String str, Boolean quoted) {
} if(str == null) {
} return String.Empty;
}
private static bool IsNonEmptyJsonArrayOrObject(string serialized)
{ StringBuilder builder = new StringBuilder(str.Length * 2);
if (serialized.Equals(EmptyObjectLiteral) || serialized.Equals(EmptyArrayLiteral)) return false; if(quoted) {
_ = builder.Append(StringQuotedChar);
// find the first position the character is not a space }
return serialized.Where(c => c != ' ').Select(c => c == OpenObjectChar || c == OpenArrayChar).FirstOrDefault();
} Escape(str, builder);
if(quoted) {
private static string Escape(string str, bool quoted) _ = builder.Append(StringQuotedChar);
{ }
if (str == null)
return string.Empty; return builder.ToString();
}
var builder = new StringBuilder(str.Length * 2);
if (quoted) builder.Append(StringQuotedChar); private static void Escape(String str, StringBuilder builder) {
Escape(str, builder); foreach(Char currentChar in str) {
if (quoted) builder.Append(StringQuotedChar); switch(currentChar) {
return builder.ToString(); case '\\':
} case '"':
case '/':
private static void Escape(string str, StringBuilder builder) _ = builder
{ .Append('\\')
foreach (var currentChar in str) .Append(currentChar);
{ break;
switch (currentChar) case '\b':
{ _ = builder.Append("\\b");
case '\\': break;
case '"': case '\t':
case '/': _ = builder.Append("\\t");
builder break;
.Append('\\') case '\n':
.Append(currentChar); _ = builder.Append("\\n");
break; break;
case '\b': case '\f':
builder.Append("\\b"); _ = builder.Append("\\f");
break; break;
case '\t': case '\r':
builder.Append("\\t"); _ = builder.Append("\\r");
break; break;
case '\n': default:
builder.Append("\\n"); if(currentChar < ' ') {
break; Byte[] escapeBytes = BitConverter.GetBytes((UInt16)currentChar);
case '\f': if(BitConverter.IsLittleEndian == false) {
builder.Append("\\f"); Array.Reverse(escapeBytes);
break; }
case '\r':
builder.Append("\\r"); _ = builder.Append("\\u")
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(
Dictionary<String, MemberInfo> fields,
private Dictionary<string, object> CreateDictionary( String targetType,
Dictionary<string, MemberInfo> fields, Object target) {
string targetType, // Create the dictionary and extract the properties
object target) Dictionary<String, Object> objectDictionary = new Dictionary<String, Object>();
{
// Create the dictionary and extract the properties if(String.IsNullOrWhiteSpace(this._options.TypeSpecifier) == false) {
var objectDictionary = new Dictionary<string, object>(); objectDictionary[this._options.TypeSpecifier] = targetType;
}
if (string.IsNullOrWhiteSpace(_options.TypeSpecifier) == false)
objectDictionary[_options.TypeSpecifier] = targetType; foreach(KeyValuePair<String, MemberInfo> field in fields) {
// Build the dictionary using property names and values
foreach (var field in fields) // Note: used to be: property.GetValue(target); but we would be reading private properties
{ try {
// Build the dictionary using property names and values objectDictionary[field.Key] = field.Value is PropertyInfo property
// Note: used to be: property.GetValue(target); but we would be reading private properties ? property.GetCacheGetMethod(this._options.IncludeNonPublic)(target)
try : (field.Value as FieldInfo)?.GetValue(target);
{ } catch {
objectDictionary[field.Key] = field.Value is PropertyInfo property /* ignored */
? property.GetCacheGetMethod(_options.IncludeNonPublic)(target) }
: (field.Value as FieldInfo)?.GetValue(target); }
}
catch return objectDictionary;
{ }
/* ignored */
} private String ResolveDictionary(IDictionary items, Int32 depth) {
} this.Append(OpenObjectChar, depth);
this.AppendLine();
return objectDictionary;
} // Iterate through the elements and output recursively
Int32 writeCount = 0;
private string ResolveDictionary(IDictionary items, int depth) foreach(Object key in items.Keys) {
{ // Serialize and append the key (first char indented)
Append(OpenObjectChar, depth); this.Append(StringQuotedChar, depth + 1);
AppendLine(); Escape(key.ToString(), this._builder);
_ = this._builder
// Iterate through the elements and output recursively
var writeCount = 0;
foreach (var key in items.Keys)
{
// Serialize and append the key (first char indented)
Append(StringQuotedChar, depth + 1);
Escape(key.ToString(), _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
Append(FieldSeparatorChar, 0); this.Append(serializedValue, 0);
AppendLine();
writeCount++; // Add a comma and start a new line -- We will remove the last one when we are done writing the elements
} this.Append(FieldSeparatorChar, 0);
this.AppendLine();
// Output the end of the object and set the result writeCount++;
RemoveLastComma(); }
Append(CloseObjectChar, writeCount > 0 ? depth : 0);
return _builder.ToString(); // Output the end of the object and set the result
} this.RemoveLastComma();
this.Append(CloseObjectChar, writeCount > 0 ? depth : 0);
private string ResolveObject(object target, int depth) return this._builder.ToString();
{ }
var targetType = target.GetType();
var fields = _options.GetProperties(targetType); private String ResolveObject(Object target, Int32 depth) {
Type targetType = target.GetType();
if (fields.Count == 0 && string.IsNullOrWhiteSpace(_options.TypeSpecifier)) Dictionary<String, MemberInfo> fields = this._options.GetProperties(targetType);
return EmptyObjectLiteral;
if(fields.Count == 0 && String.IsNullOrWhiteSpace(this._options.TypeSpecifier)) {
// If we arrive here, then we convert the object into a return EmptyObjectLiteral;
// dictionary of property names and values and call the serialization }
// function again
var objectDictionary = CreateDictionary(fields, targetType.ToString(), target); // If we arrive here, then we convert the object into a
// dictionary of property names and values and call the serialization
return Serialize(objectDictionary, depth, _options); // function again
} Dictionary<String, Object> objectDictionary = this.CreateDictionary(fields, targetType.ToString(), target);
private string ResolveEnumerable(IEnumerable target, int depth) return Serialize(objectDictionary, depth, this._options);
{ }
// Cast the items as a generic object array
var items = target.Cast<object>(); private String ResolveEnumerable(IEnumerable target, Int32 depth) {
// Cast the items as a generic object array
Append(OpenArrayChar, depth); IEnumerable<Object> items = target.Cast<Object>();
AppendLine();
this.Append(OpenArrayChar, depth);
// Iterate through the elements and output recursively this.AppendLine();
var writeCount = 0;
foreach (var entry in items) // Iterate through the elements and output recursively
{ Int32 writeCount = 0;
var serializedValue = Serialize(entry, depth + 1, _options); foreach(Object entry in items) {
String serializedValue = Serialize(entry, depth + 1, this._options);
if (IsNonEmptyJsonArrayOrObject(serializedValue))
Append(serializedValue, 0); if(IsNonEmptyJsonArrayOrObject(serializedValue)) {
else this.Append(serializedValue, 0);
Append(serializedValue, depth + 1); } else {
this.Append(serializedValue, depth + 1);
Append(FieldSeparatorChar, 0); }
AppendLine();
writeCount++; this.Append(FieldSeparatorChar, 0);
} this.AppendLine();
writeCount++;
// Output the end of the array and set the result }
RemoveLastComma();
Append(CloseArrayChar, writeCount > 0 ? depth : 0); // Output the end of the array and set the result
return _builder.ToString(); this.RemoveLastComma();
} this.Append(CloseArrayChar, writeCount > 0 ? depth : 0);
return this._builder.ToString();
private void SetIndent(int depth) }
{
if (_options.Format == false || depth <= 0) return; private void SetIndent(Int32 depth) {
if(this._options.Format == false || depth <= 0) {
_builder.Append(IndentStrings.GetOrAdd(depth, x => new string(' ', x * 4))); return;
} }
/// <summary> _ = this._builder.Append(IndentStrings.GetOrAdd(depth, x => new String(' ', x * 4)));
/// Removes the last comma in the current string builder. }
/// </summary>
private void RemoveLastComma() /// <summary>
{ /// Removes the last comma in the current string builder.
if (_builder.Length < _lastCommaSearch.Length) /// </summary>
return; private void RemoveLastComma() {
if(this._builder.Length < this._lastCommaSearch.Length) {
if (_lastCommaSearch.Where((t, i) => _builder[_builder.Length - _lastCommaSearch.Length + i] != t).Any()) return;
{ }
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 }
_builder.Remove(_builder.Length - _lastCommaSearch.Length, 1);
} // 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, int depth) }
{
SetIndent(depth); private void Append(String text, Int32 depth) {
_builder.Append(text); this.SetIndent(depth);
} _ = this._builder.Append(text);
}
private void Append(char text, int depth)
{ private void Append(Char text, Int32 depth) {
SetIndent(depth); this.SetIndent(depth);
_builder.Append(text); _ = this._builder.Append(text);
} }
private void AppendLine() private void AppendLine() {
{ if(this._options.Format == false) {
if (_options.Format == false) return; return;
_builder.Append(Environment.NewLine); }
}
_ = this._builder.Append(Environment.NewLine);
#endregion }
}
} #endregion
}
}
} }

View File

@ -1,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; namespace Unosquare.Swan.Formatters {
/// <summary>
/// <summary> /// A very simple, light-weight JSON library written by Mario
/// A very simple, light-weight JSON library written by Mario /// to teach Geo how things are done
/// to teach Geo how things are done ///
/// /// This is an useful helper for small tasks but it doesn't represent a full-featured
/// This is an useful helper for small tasks but it doesn't represent a full-featured /// serializer such as the beloved Json.NET.
/// serializer such as the beloved Json.NET. /// </summary>
/// </summary> public partial class Json {
public partial class Json private class SerializerOptions {
{ private static readonly ConcurrentDictionary<Type, Dictionary<Tuple<String, String>, MemberInfo>>
private class SerializerOptions TypeCache = new ConcurrentDictionary<Type, Dictionary<Tuple<String, String>, MemberInfo>>();
{
private static readonly ConcurrentDictionary<Type, Dictionary<Tuple<string, string>, MemberInfo>> private readonly String[] _includeProperties;
TypeCache = new ConcurrentDictionary<Type, Dictionary<Tuple<string, string>, MemberInfo>>(); private readonly String[] _excludeProperties;
private readonly Dictionary<Int32, List<WeakReference>> _parentReferences = new Dictionary<Int32, List<WeakReference>>();
private readonly string[] _includeProperties;
private readonly string[] _excludeProperties; public SerializerOptions(
private readonly Dictionary<int, List<WeakReference>> _parentReferences = new Dictionary<int, List<WeakReference>>(); Boolean format,
String typeSpecifier,
public SerializerOptions( String[] includeProperties,
bool format, String[] excludeProperties = null,
string typeSpecifier, Boolean includeNonPublic = true,
string[] includeProperties, IReadOnlyCollection<WeakReference> parentReferences = null) {
string[] excludeProperties = null, this._includeProperties = includeProperties;
bool includeNonPublic = true, this._excludeProperties = excludeProperties;
IReadOnlyCollection<WeakReference> parentReferences = null)
{ this.IncludeNonPublic = includeNonPublic;
_includeProperties = includeProperties; this.Format = format;
_excludeProperties = excludeProperties; this.TypeSpecifier = typeSpecifier;
IncludeNonPublic = includeNonPublic; if(parentReferences == null) {
Format = format; return;
TypeSpecifier = typeSpecifier; }
if (parentReferences == null) foreach(WeakReference parentReference in parentReferences.Where(x => x.IsAlive)) {
return; _ = this.IsObjectPresent(parentReference.Target);
}
foreach (var parentReference in parentReferences.Where(x => x.IsAlive)) }
{
IsObjectPresent(parentReference.Target); public Boolean Format {
} get;
} }
public String TypeSpecifier {
public bool Format { get; } get;
public string TypeSpecifier { get; } }
public bool IncludeNonPublic { get; } public Boolean IncludeNonPublic {
get;
internal bool IsObjectPresent(object target) }
{
var hashCode = target.GetHashCode(); internal Boolean IsObjectPresent(Object target) {
Int32 hashCode = target.GetHashCode();
if (_parentReferences.ContainsKey(hashCode))
{ if(this._parentReferences.ContainsKey(hashCode)) {
if (_parentReferences[hashCode].Any(p => ReferenceEquals(p.Target, target))) if(this._parentReferences[hashCode].Any(p => ReferenceEquals(p.Target, target))) {
return true; return true;
}
_parentReferences[hashCode].Add(new WeakReference(target));
return false; this._parentReferences[hashCode].Add(new WeakReference(target));
} return false;
}
_parentReferences.Add(hashCode, new List<WeakReference> { new WeakReference(target) });
return false; this._parentReferences.Add(hashCode, new List<WeakReference> { new WeakReference(target) });
} return false;
}
internal Dictionary<string, MemberInfo> GetProperties(Type targetType)
=> GetPropertiesCache(targetType) internal Dictionary<String, MemberInfo> GetProperties(Type targetType)
.When(() => _includeProperties?.Length > 0, => GetPropertiesCache(targetType)
query => query.Where(p => _includeProperties.Contains(p.Key.Item1))) .When(() => this._includeProperties?.Length > 0,
.When(() => _excludeProperties?.Length > 0, query => query.Where(p => this._includeProperties.Contains(p.Key.Item1)))
query => query.Where(p => !_excludeProperties.Contains(p.Key.Item1))) .When(() => this._excludeProperties?.Length > 0,
.ToDictionary(x => x.Key.Item2, x => x.Value); query => query.Where(p => !this._excludeProperties.Contains(p.Key.Item1)))
.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 var current)) if(TypeCache.TryGetValue(targetType, out Dictionary<Tuple<String, String>, MemberInfo> current)) {
return current; return current;
}
var fields =
new List<MemberInfo>(PropertyTypeCache.RetrieveAllProperties(targetType).Where(p => p.CanRead)); List<MemberInfo> fields =
new List<MemberInfo>(PropertyTypeCache.RetrieveAllProperties(targetType).Where(p => p.CanRead));
// If the target is a struct (value type) navigate the fields.
if (targetType.IsValueType()) // If the target is a struct (value type) navigate the fields.
{ if(targetType.IsValueType()) {
fields.AddRange(FieldTypeCache.RetrieveAllFields(targetType)); fields.AddRange(FieldTypeCache.RetrieveAllFields(targetType));
} }
var value = fields Dictionary<Tuple<String, String>, MemberInfo> value = fields
.ToDictionary( .ToDictionary(
x => Tuple.Create(x.Name, x => Tuple.Create(x.Name,
x.GetCustomAttribute<JsonPropertyAttribute>()?.PropertyName ?? x.Name), x.GetCustomAttribute<JsonPropertyAttribute>()?.PropertyName ?? x.Name),
x => x); x => x);
TypeCache.TryAdd(targetType, value); _ = TypeCache.TryAdd(targetType, value);
return 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>
internal const string AddMethodName = "Add"; /// <param name="includedNames">The included property names.</param>
/// <param name="excludedNames">The excluded property names.</param>
private const char OpenObjectChar = '{'; /// <returns>
private const char CloseObjectChar = '}'; /// A <see cref="System.String" /> that represents the current object.
/// </returns>
private const char OpenArrayChar = '['; /// <example>
private const char CloseArrayChar = ']'; /// The following example describes how to serialize a simple object.
/// <code>
private const char FieldSeparatorChar = ','; /// using Unosquare.Swan.Formatters;
private const char ValueSeparatorChar = ':'; ///
/// class Example
private const char StringEscapeChar = '\\'; /// {
private const char StringQuotedChar = '"'; /// static void Main()
/// {
private const string EmptyObjectLiteral = "{ }"; /// var obj = new { One = "One", Two = "Two" };
private const string EmptyArrayLiteral = "[ ]"; ///
private const string TrueLiteral = "true"; /// var serial = Json.Serialize(obj); // {"One": "One","Two": "Two"}
private const string FalseLiteral = "false"; /// }
private const string NullLiteral = "null"; /// }
/// </code>
#endregion /// The following example details how to serialize an object using the <see cref="JsonPropertyAttribute"/>.
/// <code>
private static readonly PropertyTypeCache PropertyTypeCache = new PropertyTypeCache(); /// using Unosquare.Swan.Attributes;
private static readonly FieldTypeCache FieldTypeCache = new FieldTypeCache(); /// using Unosquare.Swan.Formatters;
private static readonly CollectionCacheRepository<string> IgnoredPropertiesCache = new CollectionCacheRepository<string>(); ///
/// class Example
#region Public API /// {
/// class JsonPropertyExample
/// <summary> /// {
/// Serializes the specified object into a JSON string. /// [JsonProperty("data")]
/// </summary> /// public string Data { get; set; }
/// <param name="obj">The object.</param> ///
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param> /// [JsonProperty("ignoredData", true)]
/// <param name="typeSpecifier">The type specifier. Leave null or empty to avoid setting.</param> /// public string IgnoredData { get; set; }
/// <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> /// static void Main()
/// <returns> /// {
/// A <see cref="System.String" /> that represents the current object. /// var obj = new JsonPropertyExample() { Data = "OK", IgnoredData = "OK" };
/// </returns> ///
/// <example> /// // {"data": "OK"}
/// The following example describes how to serialize a simple object. /// var serializedObj = Json.Serialize(obj);
/// <code> /// }
/// using Unosquare.Swan.Formatters; /// }
/// /// </code>
/// class Example /// </example>
/// { public static String Serialize(
/// static void Main() Object obj,
/// { Boolean format = false,
/// var obj = new { One = "One", Two = "Two" }; String typeSpecifier = null,
/// Boolean includeNonPublic = false,
/// var serial = Json.Serialize(obj); // {"One": "One","Two": "Two"} String[] includedNames = null,
/// } String[] excludedNames = null) => Serialize(obj, format, typeSpecifier, includeNonPublic, includedNames, excludedNames, null);
/// }
/// </code> /// <summary>
/// The following example details how to serialize an object using the <see cref="JsonPropertyAttribute"/>. /// Serializes the specified object into a JSON string.
/// <code> /// </summary>
/// using Unosquare.Swan.Attributes; /// <param name="obj">The object.</param>
/// using Unosquare.Swan.Formatters; /// <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>
/// class Example /// <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>
/// class JsonPropertyExample /// <param name="excludedNames">The excluded property names.</param>
/// { /// <param name="parentReferences">The parent references.</param>
/// [JsonProperty("data")] /// <returns>
/// public string Data { get; set; } /// A <see cref="System.String" /> that represents the current object.
/// /// </returns>
/// [JsonProperty("ignoredData", true)] public static String Serialize(
/// public string IgnoredData { get; set; } Object obj,
/// } Boolean format,
/// String typeSpecifier,
/// static void Main() Boolean includeNonPublic,
/// { String[] includedNames,
/// var obj = new JsonPropertyExample() { Data = "OK", IgnoredData = "OK" }; String[] excludedNames,
/// List<WeakReference> parentReferences) {
/// // {"data": "OK"} if(obj != null && (obj is String || Definitions.AllBasicValueTypes.Contains(obj.GetType()))) {
/// var serializedObj = Json.Serialize(obj); return SerializePrimitiveValue(obj);
/// } }
/// }
/// </code> SerializerOptions options = new SerializerOptions(
/// </example> format,
public static string Serialize( typeSpecifier,
object obj, includedNames,
bool format = false, GetExcludedNames(obj?.GetType(), excludedNames),
string typeSpecifier = null, includeNonPublic,
bool includeNonPublic = false, parentReferences);
string[] includedNames = null,
string[] excludedNames = null) return Serializer.Serialize(obj, 0, options);
{ }
return Serialize(obj, format, typeSpecifier, includeNonPublic, includedNames, excludedNames, null);
} /// <summary>
/// Serializes the specified object only including the specified property names.
/// <summary> /// </summary>
/// Serializes the specified object into a JSON string. /// <param name="obj">The object.</param>
/// </summary> /// <param name="format">if set to <c>true</c> it formats and indents the output.</param>
/// <param name="obj">The object.</param> /// <param name="includeNames">The include names.</param>
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param> /// <returns>A <see cref="System.String" /> that represents the current object.</returns>
/// <param name="typeSpecifier">The type specifier. Leave null or empty to avoid setting.</param> /// <example>
/// <param name="includeNonPublic">if set to <c>true</c> non-public getters will be also read.</param> /// The following example shows how to serialize a simple object including the specified properties.
/// <param name="includedNames">The included property names.</param> /// <code>
/// <param name="excludedNames">The excluded property names.</param> /// using Unosquare.Swan.Formatters;
/// <param name="parentReferences">The parent references.</param> ///
/// <returns> /// class Example
/// A <see cref="System.String" /> that represents the current object. /// {
/// </returns> /// static void Main()
public static string Serialize( /// {
object obj, /// // object to serialize
bool format, /// var obj = new { One = "One", Two = "Two", Three = "Three" };
string typeSpecifier, ///
bool includeNonPublic, /// // the included names
string[] includedNames, /// var includedNames = new[] { "Two", "Three" };
string[] excludedNames, ///
List<WeakReference> parentReferences) /// // serialize only the included names
{ /// var data = Json.SerializeOnly(basicObject, true, includedNames);
if (obj != null && (obj is string || Definitions.AllBasicValueTypes.Contains(obj.GetType()))) /// // {"Two": "Two","Three": "Three" }
{ /// }
return SerializePrimitiveValue(obj); /// }
} /// </code>
/// </example>
var options = new SerializerOptions( public static String SerializeOnly(Object obj, Boolean format, params String[] includeNames) {
format, SerializerOptions options = new SerializerOptions(format, null, includeNames);
typeSpecifier,
includedNames, return Serializer.Serialize(obj, 0, options);
GetExcludedNames(obj?.GetType(), excludedNames), }
includeNonPublic,
parentReferences); /// <summary>
/// Serializes the specified object excluding the specified property names.
return Serializer.Serialize(obj, 0, options); /// </summary>
} /// <param name="obj">The object.</param>
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param>
/// <summary> /// <param name="excludeNames">The exclude names.</param>
/// Serializes the specified object only including the specified property names. /// <returns>A <see cref="System.String" /> that represents the current object.</returns>
/// </summary> /// <example>
/// <param name="obj">The object.</param> /// The following code shows how to serialize a simple object exluding the specified properties.
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param> /// <code>
/// <param name="includeNames">The include names.</param> /// using Unosquare.Swan.Formatters;
/// <returns>A <see cref="System.String" /> that represents the current object.</returns> ///
/// <example> /// class Example
/// The following example shows how to serialize a simple object including the specified properties. /// {
/// <code> /// static void Main()
/// using Unosquare.Swan.Formatters; /// {
/// /// // object to serialize
/// class Example /// var obj = new { One = "One", Two = "Two", Three = "Three" };
/// { ///
/// static void Main() /// // the excluded names
/// { /// var excludeNames = new[] { "Two", "Three" };
/// // object to serialize ///
/// var obj = new { One = "One", Two = "Two", Three = "Three" }; /// // serialize excluding
/// /// var data = Json.SerializeExcluding(basicObject, false, includedNames);
/// // the included names /// // {"One": "One"}
/// var includedNames = new[] { "Two", "Three" }; /// }
/// /// }
/// // serialize only the included names /// </code>
/// var data = Json.SerializeOnly(basicObject, true, includedNames); /// </example>
/// // {"Two": "Two","Three": "Three" } public static String SerializeExcluding(Object obj, Boolean format, params String[] excludeNames) {
/// } SerializerOptions options = new SerializerOptions(format, null, null, excludeNames);
/// }
/// </code> return Serializer.Serialize(obj, 0, options);
/// </example> }
public static string SerializeOnly(object obj, bool format, params string[] includeNames)
{ /// <summary>
var options = new SerializerOptions(format, null, includeNames); /// Deserializes the specified json string as either a Dictionary[string, object] or as a List[object]
/// depending on the syntax of the JSON string.
return Serializer.Serialize(obj, 0, options); /// </summary>
} /// <param name="json">The json.</param>
/// <returns>Type of the current deserializes.</returns>
/// <summary> /// <example>
/// Serializes the specified object excluding the specified property names. /// The following code shows how to deserialize a JSON string into a Dictionary.
/// </summary> /// <code>
/// <param name="obj">The object.</param> /// using Unosquare.Swan.Formatters;
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param> ///
/// <param name="excludeNames">The exclude names.</param> /// class Example
/// <returns>A <see cref="System.String" /> that represents the current object.</returns> /// {
/// <example> /// static void Main()
/// The following code shows how to serialize a simple object exluding the specified properties. /// {
/// <code> /// // json to deserialize
/// using Unosquare.Swan.Formatters; /// var basicJson = "{\"One\":\"One\",\"Two\":\"Two\",\"Three\":\"Three\"}";
/// ///
/// class Example /// // deserializes the specified json into a Dictionary&lt;string, object&gt;.
/// { /// var data = Json.Deserialize(basicJson);
/// static void Main() /// }
/// { /// }
/// // object to serialize /// </code>
/// var obj = new { One = "One", Two = "Two", Three = "Three" }; /// </example>
/// public static Object Deserialize(String json) => Deserializer.DeserializeInternal(json);
/// // the excluded names
/// var excludeNames = new[] { "Two", "Three" }; /// <summary>
/// /// Deserializes the specified json string and converts it to the specified object type.
/// // serialize excluding /// Non-public constructors and property setters are ignored.
/// var data = Json.SerializeExcluding(basicObject, false, includedNames); /// </summary>
/// // {"One": "One"} /// <typeparam name="T">The type of object to deserialize.</typeparam>
/// } /// <param name="json">The json.</param>
/// } /// <returns>The deserialized specified type object.</returns>
/// </code> /// <example>
/// </example> /// The following code describes how to deserialize a JSON string into an object of type T.
public static string SerializeExcluding(object obj, bool format, params string[] excludeNames) /// <code>
{ /// using Unosquare.Swan.Formatters;
var options = new SerializerOptions(format, null, null, excludeNames); ///
/// class Example
return Serializer.Serialize(obj, 0, options); /// {
} /// static void Main()
/// {
/// <summary> /// // json type BasicJson to serialize
/// Deserializes the specified json string as either a Dictionary[string, object] or as a List[object] /// var basicJson = "{\"One\":\"One\",\"Two\":\"Two\",\"Three\":\"Three\"}";
/// depending on the syntax of the JSON string. ///
/// </summary> /// // deserializes the specified string in a new instance of the type BasicJson.
/// <param name="json">The json.</param> /// var data = Json.Deserialize&lt;BasicJson&gt;(basicJson);
/// <returns>Type of the current deserializes.</returns> /// }
/// <example> /// }
/// The following code shows how to deserialize a JSON string into a Dictionary. /// </code>
/// <code> /// </example>
/// using Unosquare.Swan.Formatters; public static T Deserialize<T>(String json) => (T)Deserialize(json, typeof(T));
///
/// class Example /// <summary>
/// { /// Deserializes the specified json string and converts it to the specified object type.
/// static void Main() /// </summary>
/// { /// <typeparam name="T">The type of object to deserialize.</typeparam>
/// // json to deserialize /// <param name="json">The json.</param>
/// var basicJson = "{\"One\":\"One\",\"Two\":\"Two\",\"Three\":\"Three\"}"; /// <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>
/// // deserializes the specified json into a Dictionary&lt;string, object&gt;. public static T Deserialize<T>(String json, Boolean includeNonPublic) => (T)Deserialize(json, typeof(T), includeNonPublic);
/// var data = Json.Deserialize(basicJson);
/// } /// <summary>
/// } /// Deserializes the specified json string and converts it to the specified object type.
/// </code> /// </summary>
/// </example> /// <param name="json">The json.</param>
public static object Deserialize(string json) => Deserializer.DeserializeInternal(json); /// <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>
/// <summary> /// <returns>Type of the current conversion from json result.</returns>
/// Deserializes the specified json string and converts it to the specified object type. public static Object Deserialize(String json, Type resultType, Boolean includeNonPublic = false)
/// Non-public constructors and property setters are ignored. => Converter.FromJsonResult(Deserializer.DeserializeInternal(json), resultType, includeNonPublic);
/// </summary>
/// <typeparam name="T">The type of object to deserialize.</typeparam> #endregion
/// <param name="json">The json.</param>
/// <returns>The deserialized specified type object.</returns> #region Private API
/// <example>
/// The following code describes how to deserialize a JSON string into an object of type T. private static String[] GetExcludedNames(Type type, String[] excludedNames) {
/// <code> if(type == null) {
/// using Unosquare.Swan.Formatters; return excludedNames;
/// }
/// class Example
/// { IEnumerable<String> excludedByAttr = IgnoredPropertiesCache.Retrieve(type, t => t.GetProperties()
/// 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) private static String SerializePrimitiveValue(Object obj) {
{ switch(obj) {
switch (obj) case String stringValue:
{ return stringValue;
case string stringValue: case Boolean boolValue:
return stringValue; return boolValue ? TrueLiteral : FalseLiteral;
case bool boolValue: default:
return boolValue ? TrueLiteral : FalseLiteral; return obj.ToString();
default: }
return obj.ToString(); }
} #endregion
} }
#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>>>(() => /// <summary>
new ConcurrentDictionary<Tuple<object, Type>, IEnumerable<object>>(), true); /// A PropertyTypeCache object for caching properties and their attributes.
/// </summary>
/// <summary> public PropertyTypeCache PropertyTypeCache {
/// Initializes a new instance of the <see cref="AttributeCache"/> class. get;
/// </summary> }
/// <param name="propertyCache">The property cache object.</param>
public AttributeCache(PropertyTypeCache propertyCache = null) /// <summary>
{ /// Determines whether [contains] [the specified member].
PropertyTypeCache = propertyCache ?? Runtime.PropertyTypeCache; /// </summary>
} /// <typeparam name="T">The type of the attribute to be retrieved.</typeparam>
/// <param name="member">The member.</param>
/// <summary> /// <returns>
/// A PropertyTypeCache object for caching properties and their attributes. /// <c>true</c> if [contains] [the specified member]; otherwise, <c>false</c>.
/// </summary> /// </returns>
public PropertyTypeCache PropertyTypeCache { get; } 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) {
/// <summary> throw new ArgumentNullException(nameof(member));
/// Gets specific attributes from a member constrained to an attribute. }
/// </summary>
/// <typeparam name="T">The type of the attribute to be retrieved.</typeparam> return this.Retrieve(new Tuple<Object, Type>(member, typeof(T)), t => member.GetCustomAttributes<T>(inherit));
/// <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> /// <summary>
public IEnumerable<object> Retrieve<T>(MemberInfo member, bool inherit = false) /// Gets all attributes of a specific type from a member.
where T : Attribute /// </summary>
{ /// <param name="member">The member.</param>
if (member == null) /// <param name="type">The attribute type.</param>
throw new ArgumentNullException(nameof(member)); /// <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>
return Retrieve(new Tuple<object, Type>(member, typeof(T)), t => member.GetCustomAttributes<T>(inherit)); public IEnumerable<Object> Retrieve(MemberInfo member, Type type, Boolean inherit = false) {
} if(member == null) {
throw new ArgumentNullException(nameof(member));
/// <summary> }
/// Gets all attributes of a specific type from a member.
/// </summary> if(type == null) {
/// <param name="member">The member.</param> throw new ArgumentNullException(nameof(type));
/// <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> return this.Retrieve(
public IEnumerable<object> Retrieve(MemberInfo member, Type type, bool inherit = false) new Tuple<Object, Type>(member, type),
{ t => member.GetCustomAttributes(type, inherit));
if (member == null) }
throw new ArgumentNullException(nameof(member));
/// <summary>
if (type == null) /// Gets one attribute of a specific type from a member.
throw new ArgumentNullException(nameof(type)); /// </summary>
/// <typeparam name="T">The attribute type.</typeparam>
return Retrieve( /// <param name="member">The member.</param>
new Tuple<object, Type>(member, type), /// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param>
t => member.GetCustomAttributes(type, inherit)); /// <returns>An attribute stored for the specified type.</returns>
} public T RetrieveOne<T>(MemberInfo member, Boolean inherit = false)
where T : Attribute {
/// <summary> if(member == null) {
/// Gets one attribute of a specific type from a member. return default;
/// </summary> }
/// <typeparam name="T">The attribute type.</typeparam>
/// <param name="member">The member.</param> IEnumerable<Object> attr = this.Retrieve(
/// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param> new Tuple<Object, Type>(member, typeof(T)),
/// <returns>An attribute stored for the specified type.</returns> t => member.GetCustomAttributes(typeof(T), inherit));
public T RetrieveOne<T>(MemberInfo member, bool inherit = false)
where T : Attribute return ConvertToAttribute<T>(attr);
{ }
if (member == null)
return default; /// <summary>
/// Gets one attribute of a specific type from a generic type.
var attr = Retrieve( /// </summary>
new Tuple<object, Type>(member, typeof(T)), /// <typeparam name="TAttribute">The type of the attribute.</typeparam>
t => member.GetCustomAttributes(typeof(T), inherit)); /// <typeparam name="T">The type to retrieve the attribute.</typeparam>
/// <param name="inherit">if set to <c>true</c> [inherit].</param>
return ConvertToAttribute<T>(attr); /// <returns>An attribute stored for the specified type.</returns>
} public TAttribute RetrieveOne<TAttribute, T>(Boolean inherit = false)
where TAttribute : Attribute {
/// <summary> IEnumerable<Object> attr = this.Retrieve(
/// Gets one attribute of a specific type from a generic type. new Tuple<Object, Type>(typeof(T), typeof(TAttribute)),
/// </summary> t => typeof(T).GetCustomAttributes(typeof(TAttribute), inherit));
/// <typeparam name="TAttribute">The type of the attribute.</typeparam>
/// <typeparam name="T">The type to retrieve the attribute.</typeparam> return ConvertToAttribute<TAttribute>(attr);
/// <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) /// <summary>
where TAttribute : Attribute /// Gets all properties an their attributes of a given type constrained to only attributes.
{ /// </summary>
var attr = Retrieve( /// <typeparam name="T">The type of the attribute to retrieve.</typeparam>
new Tuple<object, Type>(typeof(T), typeof(TAttribute)), /// <param name="type">The type of the object.</param>
t => typeof(T).GetCustomAttributes(typeof(TAttribute), inherit)); /// <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>
return ConvertToAttribute<TAttribute>(attr); public Dictionary<PropertyInfo, IEnumerable<Object>> Retrieve<T>(Type type, Boolean inherit = false)
} where T : Attribute {
if(type == null) {
/// <summary> throw new ArgumentNullException(nameof(type));
/// 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> return this.PropertyTypeCache.RetrieveAllProperties(type, true)
/// <param name="type">The type of the object.</param> .ToDictionary(x => x, x => this.Retrieve<T>(x, inherit));
/// <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) /// <summary>
where T : Attribute /// Gets all properties and their attributes of a given type.
{ /// </summary>
if (type == null) /// <typeparam name="T">The object type used to extract the properties from.</typeparam>
throw new ArgumentNullException(nameof(type)); /// <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>
return PropertyTypeCache.RetrieveAllProperties(type, true) /// <returns>
.ToDictionary(x => x, x => Retrieve<T>(x, inherit)); /// 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) {
/// <summary> if(attributeType == null) {
/// Gets all properties and their attributes of a given type. throw new ArgumentNullException(nameof(attributeType));
/// </summary> }
/// <typeparam name="T">The object type used to extract the properties from.</typeparam>
/// <param name="attributeType">Type of the attribute.</param> return this.PropertyTypeCache.RetrieveAllProperties<T>(true)
/// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param> .ToDictionary(x => x, x => this.Retrieve(x, attributeType, inherit));
/// <returns> }
/// A dictionary of the properties and their attributes stored for the specified type.
/// </returns> private static T ConvertToAttribute<T>(IEnumerable<Object> attr)
public Dictionary<PropertyInfo, IEnumerable<object>> RetrieveFromType<T>(Type attributeType, bool inherit = false) where T : Attribute {
{ if(attr?.Any() != true) {
if (attributeType == null) return default;
throw new ArgumentNullException(nameof(attributeType)); }
return PropertyTypeCache.RetrieveAllProperties<T>(true) if(attr.Count() == 1) {
.ToDictionary(x => x, x => Retrieve(x, attributeType, inherit)); return (T)Convert.ChangeType(attr.First(), typeof(T));
} }
private static T ConvertToAttribute<T>(IEnumerable<object> attr) throw new AmbiguousMatchException("Multiple custom attributes of the same type found.");
where T : Attribute }
{
if (attr?.Any() != true) private IEnumerable<Object> Retrieve(Tuple<Object, Type> key, Func<Tuple<Object, Type>, IEnumerable<Object>> factory) {
return default; if(factory == null) {
throw new ArgumentNullException(nameof(factory));
if (attr.Count() == 1) }
return (T) Convert.ChangeType(attr.First(), typeof(T));
return this._data.Value.GetOrAdd(key, k => factory.Invoke(k).Where(item => item != null));
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));
}
}
} }

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) this.Property = propertyInfo.Name;
{ this.DataType = propertyInfo.PropertyType.Name;
if (propertyInfo == null)
{ foreach(PropertyDisplayAttribute display in Runtime.AttributeCache.Retrieve<PropertyDisplayAttribute>(propertyInfo, true)) {
throw new ArgumentNullException(nameof(propertyInfo)); this.Name = display.Name;
} this.Description = display.Description;
this.GroupName = display.GroupName;
Property = propertyInfo.Name; this.DefaultValue = display.DefaultValue;
DataType = propertyInfo.PropertyType.Name; }
}
foreach (PropertyDisplayAttribute display in Runtime.AttributeCache.Retrieve<PropertyDisplayAttribute>(propertyInfo, true))
{
Name = display.Name;
Description = display.Description;
GroupName = display.GroupName;
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) /// <summary>
: base(typeof(T).GetProperty(property)) /// 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)) {
}
}
} }

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