Refactor CapyKit utilities: streamline namespaces, improve code consistency, and enhance XML documentation.

This commit is contained in:
Jordan Wages 2026-07-23 19:53:33 -05:00
commit f03f73afc7
28 changed files with 5180 additions and 4298 deletions

View file

@ -1,36 +1,19 @@
using System; namespace CapyKit.Attributes;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CapyKit.Attributes /// <summary>
/// Custom attribute class for decorating enumeration fields with additional data.
/// </summary>
/// <typeparam name="T">
/// Generic type parameter allowing for arbitrary declarations and assignments of meaning.
/// </typeparam>
[AttributeUsage(AttributeTargets.Field)]
public abstract class EnumerationAttribute<T> : Attribute
{ {
/// <summary>
/// Custom attribute class for decorating enumeration fields with additional data.
/// </summary>
/// <typeparam name="T">
/// Generic type parameter allowing for arbitrary declarations and assignments of meaning.
/// </typeparam>
[AttributeUsage(AttributeTargets.Field)]
public abstract class EnumerationAttribute<T> : Attribute
{
#region Properties
/// <summary>
/// Initializes a new instance of the <see cref="EnumerationAttribute{T}"/> class with a
/// specified value.
/// </summary>
/// <value> The value. </value>
public T Value { get; private set; }
#endregion
#region Constructors #region Constructors
/// <summary> Gets the value of the enumeration represented by this attribute. </summary> /// <summary> Gets the value of the enumeration represented by this attribute. </summary>
/// <param name="value"> /// <param name="value">
/// Initializes a new instance of the <see cref="EnumerationAttribute{T}"/> class with a /// Initializes a new instance of the <see cref="EnumerationAttribute{T}" /> class with a
/// specified value. /// specified value.
/// </param> /// </param>
protected EnumerationAttribute(T value) protected EnumerationAttribute(T value)
@ -39,5 +22,15 @@ namespace CapyKit.Attributes
} }
#endregion #endregion
}
#region Properties
/// <summary>
/// Initializes a new instance of the <see cref="EnumerationAttribute{T}" /> class with a
/// specified value.
/// </summary>
/// <value> The value. </value>
public T Value { get; private set; }
#endregion
} }

View file

@ -1,18 +1,12 @@
using System; namespace CapyKit.Attributes;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CapyKit.Attributes /// <summary> An attribute class for decorating enumeration fields with a description. </summary>
/// <seealso cref="EnumerationAttribute{T}" />
[AttributeUsage(AttributeTargets.Field)]
public class EnumerationDescriptionAttribute : EnumerationAttribute<string>
{ {
/// <summary> An attribute class for decorating enumeration fields with a description. </summary>
/// <seealso cref="EnumerationAttribute{T}"/>
[AttributeUsage(AttributeTargets.Field)]
public class EnumerationDescriptionAttribute : EnumerationAttribute<string>
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="EnumerationDescriptionAttribute"/> class with /// Initializes a new instance of the <see cref="EnumerationDescriptionAttribute" /> class with
/// the specified description. /// the specified description.
/// </summary> /// </summary>
/// <param name="description"> The description of the enumeration value. </param> /// <param name="description"> The description of the enumeration value. </param>
@ -21,5 +15,4 @@ namespace CapyKit.Attributes
{ {
// //
} }
}
} }

View file

@ -1,17 +1,17 @@
namespace CapyKit.Attributes; namespace CapyKit.Attributes;
/// <summary> Attribute describing the associated font-awesome icon of an <see cref="Enum"/> parameter. </summary> /// <summary> Attribute describing the associated font-awesome icon of an <see cref="Enum" /> parameter. </summary>
[AttributeUsage(AttributeTargets.Field)] [AttributeUsage(AttributeTargets.Field)]
public class EnumerationIconAttribute : Attribute public class EnumerationIconAttribute : Attribute
{ {
/// <summary> Gets or sets the description of the enumeration. </summary>
/// <value> The description. </value>
public string Icon { get; private set; }
/// <summary> Constructor. </summary> /// <summary> Constructor. </summary>
/// <param name="icon"> The icon associated with the enumeration. </param> /// <param name="icon"> The icon associated with the enumeration. </param>
public EnumerationIconAttribute(string icon) public EnumerationIconAttribute(string icon)
{ {
this.Icon = icon; this.Icon = icon;
} }
/// <summary> Gets or sets the description of the enumeration. </summary>
/// <value> The description. </value>
public string Icon { get; private set; }
} }

View file

@ -4,14 +4,6 @@ namespace CapyKit.Attributes;
/// <remarks> Accepted values are stored as discreet values similar to an enumeration. Ranges are not supported. </remarks> /// <remarks> Accepted values are stored as discreet values similar to an enumeration. Ranges are not supported. </remarks>
public class ParameterAcceptedValuesAttribute : Attribute public class ParameterAcceptedValuesAttribute : Attribute
{ {
#region Properties
/// <summary> Gets or sets the accepted values. </summary>
/// <value> The accepted values. </value>
public IEnumerable<object> AcceptedValues { get; private set; }
#endregion Properties
#region Constructors #region Constructors
/// <summary> Constructor. </summary> /// <summary> Constructor. </summary>
@ -22,4 +14,12 @@ public class ParameterAcceptedValuesAttribute : Attribute
} }
#endregion Constructors #endregion Constructors
#region Properties
/// <summary> Gets or sets the accepted values. </summary>
/// <value> The accepted values. </value>
public IEnumerable<object> AcceptedValues { get; private set; }
#endregion Properties
} }

View file

@ -13,13 +13,15 @@ public class ParameterAttribute : Attribute
#region Properties #region Properties
/// <summary> /// <summary>
/// Gets or sets the name parameter as passed in a POST method from the Browser. This can be hardcoded at the class level. /// Gets or sets the name parameter as passed in a POST method from the Browser. This can be hardcoded at the class
/// level.
/// </summary> /// </summary>
/// <value> The name of the parameter. </value> /// <value> The name of the parameter. </value>
public string ParameterName { get; private set; } public string ParameterName { get; private set; }
/// <summary> /// <summary>
/// Gets the description of the parameter. This serves as a human-readable explanation or label for the parameter's purpose or usage. /// Gets the description of the parameter. This serves as a human-readable explanation or label for the parameter's
/// purpose or usage.
/// </summary> /// </summary>
/// <value>The description text of the parameter.</value> /// <value>The description text of the parameter.</value>
public string Description { get; private set; } public string Description { get; private set; }
@ -48,8 +50,10 @@ public class ParameterAttribute : Attribute
/// <summary> Constructor. </summary> /// <summary> Constructor. </summary>
/// <param name="systemName"> The name of the system. </param> /// <param name="systemName"> The name of the system. </param>
/// <param name="description"> Name of the resource that contains the description of /// <param name="description">
/// the parameter. </param> /// Name of the resource that contains the description of
/// the parameter.
/// </param>
public ParameterAttribute(string systemName, string description) public ParameterAttribute(string systemName, string description)
{ {
this.ParameterName = systemName; this.ParameterName = systemName;
@ -60,8 +64,10 @@ public class ParameterAttribute : Attribute
/// <summary> Constructor. </summary> /// <summary> Constructor. </summary>
/// <param name="systemName"> The name of the system. </param> /// <param name="systemName"> The name of the system. </param>
/// <param name="description"> Name of the resource that contains the description of /// <param name="description">
/// the parameter. </param> /// Name of the resource that contains the description of
/// the parameter.
/// </param>
/// <param name="iconName"> The FontAwesome icon name. </param> /// <param name="iconName"> The FontAwesome icon name. </param>
public ParameterAttribute(string systemName, string description, string iconName) public ParameterAttribute(string systemName, string description, string iconName)
{ {
@ -73,8 +79,10 @@ public class ParameterAttribute : Attribute
/// <summary> Constructor. </summary> /// <summary> Constructor. </summary>
/// <param name="systemName"> The name of the system. </param> /// <param name="systemName"> The name of the system. </param>
/// <param name="description"> Name of the resource that contains the description of /// <param name="description">
/// the parameter. </param> /// Name of the resource that contains the description of
/// the parameter.
/// </param>
/// <param name="iconName"> The FontAwesome icon name. </param> /// <param name="iconName"> The FontAwesome icon name. </param>
/// <param name="ordinal"> The ordinal position. </param> /// <param name="ordinal"> The ordinal position. </param>
public ParameterAttribute(string systemName, string description, string iconName, int ordinal) public ParameterAttribute(string systemName, string description, string iconName, int ordinal)
@ -87,8 +95,10 @@ public class ParameterAttribute : Attribute
/// <summary> Constructor. </summary> /// <summary> Constructor. </summary>
/// <param name="systemName"> The name of the system. </param> /// <param name="systemName"> The name of the system. </param>
/// <param name="description"> Name of the resource that contains the description of /// <param name="description">
/// the parameter. </param> /// Name of the resource that contains the description of
/// the parameter.
/// </param>
/// <param name="ordinal"> The ordinal position. </param> /// <param name="ordinal"> The ordinal position. </param>
public ParameterAttribute(string systemName, string description, int ordinal) public ParameterAttribute(string systemName, string description, int ordinal)
{ {

View file

@ -1,48 +1,19 @@
using System; namespace CapyKit.Attributes;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CapyKit.Attributes /// <summary>
/// Custom attribute for formatting values in a specific way.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class ValueFormatAttribute : Attribute
{ {
/// <summary>
/// Custom attribute for formatting values in a specific way.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class ValueFormatAttribute : Attribute
{
#region Properties #region Properties
/// <summary> Gets or sets the format to use for formatting the value. </summary> /// <summary> Gets or sets the format to use for formatting the value. </summary>
/// <value> The format string used to format the value. </value> /// <value> The format string used to format the value. </value>
public string Format { get; private set; } public string Format { get; }
#endregion Properties #endregion Properties
#region Constructors
/// <summary>
/// Default constructor. Initializes a new instance of the <see cref="ValueFormatAttribute"/>
/// class with an empty format string.
/// </summary>
public ValueFormatAttribute()
{
this.Format = string.Empty;
}
/// <summary>
/// Constructor. Initializes a new instance of the <see cref="ValueFormatAttribute"/> class with
/// the specified format string.
/// </summary>
/// <param name="format"> The format string used to format the value. </param>
public ValueFormatAttribute(string format)
{
this.Format = format;
}
#endregion Constructors
#region Methods #region Methods
/// <summary> Gets a parameterized formatted string for the specified index. </summary> /// <summary> Gets a parameterized formatted string for the specified index. </summary>
@ -54,5 +25,27 @@ namespace CapyKit.Attributes
} }
#endregion Methods #endregion Methods
#region Constructors
/// <summary>
/// Default constructor. Initializes a new instance of the <see cref="ValueFormatAttribute" />
/// class with an empty format string.
/// </summary>
public ValueFormatAttribute()
{
this.Format = string.Empty;
} }
/// <summary>
/// Constructor. Initializes a new instance of the <see cref="ValueFormatAttribute" /> class with
/// the specified format string.
/// </summary>
/// <param name="format"> The format string used to format the value. </param>
public ValueFormatAttribute(string format)
{
this.Format = format;
}
#endregion Constructors
} }

View file

@ -1,34 +1,29 @@
using System; using System.Runtime.CompilerServices;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using CapyKit.Attributes; using CapyKit.Attributes;
using CapyKit.Extensions;
namespace CapyKit namespace CapyKit;
/// <summary>
/// The CapyEventReporter class is responsible for managing event subscriptions and emissions within CapyKit.
/// </summary>
/// <remarks>
/// Because consumers of CapyKit may have varied ways of handling logging, the <see cref="CapyEventReporter" />
/// provides
/// a way for subscribers to recieve events for various "events" within the library. These can be thought of as
/// a logging solution for CapyKit. Consumers are free to treat these events however they see fit.
/// </remarks>
public static class CapyEventReporter
{ {
/// <summary>
/// The CapyEventReporter class is responsible for managing event subscriptions and emissions within CapyKit.
/// </summary>
/// <remarks>
/// Because consumers of CapyKit may have varied ways of handling logging, the <see cref="CapyEventReporter"/> provides
/// a way for subscribers to recieve events for various "events" within the library. These can be thought of as
/// a logging solution for CapyKit. Consumers are free to treat these events however they see fit.
/// </remarks>
public static class CapyEventReporter
{
#region Members #region Members
/// <summary> /// <summary>
/// A dictionary storing event handlers and their corresponding origins for each subscription level. /// A dictionary storing event handlers and their corresponding origins for each subscription level.
/// </summary> /// </summary>
private static Dictionary<EventLevel, List<(CapyEventHandler Handler, string origin)>> subscribers = new Dictionary<EventLevel, List<(CapyEventHandler Handler, string origin)>>(); private static readonly Dictionary<EventLevel, List<(CapyEventHandler Handler, string origin)>> subscribers = new();
/// <summary> A hash set storing unique identifiers for events intended to only be emitted once. </summary> /// <summary> A hash set storing unique identifiers for events intended to only be emitted once. </summary>
/// <seealso cref="EmitEventOnce(EventLevel, string, string, string, object[])"/> /// <seealso cref="EmitEventOnce(EventLevel, string, string, string, object[])" />
private static HashSet<string> uniqueIdentifiers = new HashSet<string>(); private static readonly HashSet<string> uniqueIdentifiers = new();
#endregion #endregion
@ -47,14 +42,15 @@ namespace CapyKit
/// <param name="origin"> /// <param name="origin">
/// (Optional) The name of the method or class where the subscription is made. /// (Optional) The name of the method or class where the subscription is made.
/// </param> /// </param>
public static void Subscribe(CapyEventHandler callback, EventLevel subscriptionLevel, [CallerMemberName] string origin = null) public static void Subscribe(CapyEventHandler callback, EventLevel subscriptionLevel,
[CallerMemberName] string origin = null)
{ {
if (!subscribers.ContainsKey(subscriptionLevel)) if (!CapyEventReporter.subscribers.ContainsKey(subscriptionLevel))
{ {
subscribers.Add(subscriptionLevel, new List<(CapyEventHandler Handler, string origin)>()); CapyEventReporter.subscribers.Add(subscriptionLevel, new List<(CapyEventHandler Handler, string origin)>());
} }
subscribers[subscriptionLevel].Add((callback, origin ?? "[Unknown]")); CapyEventReporter.subscribers[subscriptionLevel].Add((callback, origin ?? "[Unknown]"));
} }
/// <summary> /// <summary>
@ -70,19 +66,20 @@ namespace CapyKit
{ {
if (value is EventLevel) if (value is EventLevel)
{ {
subscribers[(EventLevel)value].RemoveAll(c => c.Handler == callback && c.origin == origin); CapyEventReporter.subscribers[(EventLevel)value]
.RemoveAll(c => c.Handler == callback && c.origin == origin);
} }
} }
} }
/// <summary> Emits an event with the given severity level, message, and method name. </summary> /// <summary> Emits an event with the given severity level, message, and method name. </summary>
/// <remarks> /// <remarks>
/// In order to allow for efficient calling member access via <see cref="CallerMemberNameAttribute"/> /// In order to allow for efficient calling member access via <see cref="CallerMemberNameAttribute" />
/// , it is suggested that <paramref name="args"/> is defined explicitly for formatted messages. /// , it is suggested that <paramref name="args" /> is defined explicitly for formatted messages.
/// </remarks> /// </remarks>
/// <param name="eventLevel"> The severity level of the event. </param> /// <param name="eventLevel"> The severity level of the event. </param>
/// <param name="message"> /// <param name="message">
/// The message describing the reason for the event. String formatting for <paramref name="args"/> /// The message describing the reason for the event. String formatting for <paramref name="args" />
/// is accepted. /// is accepted.
/// </param> /// </param>
/// <param name="method"> /// <param name="method">
@ -96,10 +93,11 @@ namespace CapyKit
/// CapyEventReporter.EmitEvent(EventLevel.Error, "Could not find the description for {0}.", args: new[] { enumeration }); /// CapyEventReporter.EmitEvent(EventLevel.Error, "Could not find the description for {0}.", args: new[] { enumeration });
/// </code> /// </code>
/// </example> /// </example>
/// <seealso cref="CallerMemberNameAttribute"/> /// <seealso cref="CallerMemberNameAttribute" />
public static void EmitEvent(EventLevel eventLevel, string message, [CallerMemberName] string method = null, params object[] args) public static void EmitEvent(EventLevel eventLevel, string message, [CallerMemberName] string method = null,
params object[] args)
{ {
if (!subscribers.ContainsKey(eventLevel)) if (!CapyEventReporter.subscribers.ContainsKey(eventLevel))
{ {
return; return;
} }
@ -108,7 +106,7 @@ namespace CapyKit
var capyEventArgs = new CapyEventArgs(eventLevel, formattedMessage, method); var capyEventArgs = new CapyEventArgs(eventLevel, formattedMessage, method);
foreach (var subscriber in subscribers[eventLevel]) foreach (var subscriber in CapyEventReporter.subscribers[eventLevel])
{ {
subscriber.Handler(capyEventArgs); subscriber.Handler(capyEventArgs);
} }
@ -119,13 +117,13 @@ namespace CapyKit
/// time. /// time.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This method is similar to <see cref="EmitEvent(EventLevel, string, string, string, object[])"/> /// This method is similar to <see cref="EmitEvent(EventLevel, string, string, string, object[])" />
/// , but requires a unique identifier (such as a <see cref="Guid"/>) to prevent duplicate /// , but requires a unique identifier (such as a <see cref="Guid" />) to prevent duplicate
/// emissions. /// emissions.
/// </remarks> /// </remarks>
/// <param name="eventLevel"> The severity level of the event. </param> /// <param name="eventLevel"> The severity level of the event. </param>
/// <param name="message"> /// <param name="message">
/// The message describing the reason for the event. String formatting for <paramref name="args"/> /// The message describing the reason for the event. String formatting for <paramref name="args" />
/// is accepted. /// is accepted.
/// </param> /// </param>
/// <param name="uniqueIdentifier"> A unique identifier for the event emission. </param> /// <param name="uniqueIdentifier"> A unique identifier for the event emission. </param>
@ -135,34 +133,52 @@ namespace CapyKit
/// <param name="args"> /// <param name="args">
/// A variable-length parameters list containing arguments for formatting the message. /// A variable-length parameters list containing arguments for formatting the message.
/// </param> /// </param>
/// <seealso cref="CallerMemberNameAttribute"/> /// <seealso cref="CallerMemberNameAttribute" />
/// <seealso cref="Guid"/> /// <seealso cref="Guid" />
public static void EmitEventOnce(EventLevel eventLevel, string message, string uniqueIdentifier, [CallerMemberName] string method = null, params object[] args) public static void EmitEventOnce(EventLevel eventLevel, string message, string uniqueIdentifier,
[CallerMemberName] string method = null, params object[] args)
{ {
if(uniqueIdentifiers.Contains(uniqueIdentifier)) if (CapyEventReporter.uniqueIdentifiers.Contains(uniqueIdentifier))
{ {
return; return;
} }
uniqueIdentifiers.Add(uniqueIdentifier); CapyEventReporter.uniqueIdentifiers.Add(uniqueIdentifier);
EmitEvent(eventLevel, message, method: method, args: args); EmitEvent(eventLevel, message, method, args);
} }
#endregion #endregion
}
/// <summary>
/// A delegate representing an event handler that accepts a <see cref="CapyEventArgs" /> instance.
/// </summary>
/// <param name="e">The CapyEventArgs instance containing event data.</param>
public delegate void CapyEventHandler(CapyEventArgs e);
/// <summary>
/// The CapyEventArgs class represents an event argument instance with event level, message, and
/// method name information.
/// </summary>
public class CapyEventArgs : EventArgs
{
#region Constructor
/// <summary>
/// Initializes a new instance of the CapyEventArgs class with the specified event level, message, and method name.
/// </summary>
/// <param name="level">The severity level of the event.</param>
/// <param name="message">A descriptive message explaining the reason for the event.</param>
/// <param name="method">The name of the method where the event was raised.</param>
public CapyEventArgs(EventLevel level, string message, string method = null)
{
this.Level = level;
this.Message = message;
this.MethodName = method ?? "[Unknown]";
} }
/// <summary> #endregion
/// A delegate representing an event handler that accepts a <see cref="CapyEventArgs"/> instance.
/// </summary>
/// <param name="e">The CapyEventArgs instance containing event data.</param>
public delegate void CapyEventHandler(CapyEventArgs e);
/// <summary>
/// The CapyEventArgs class represents an event argument instance with event level, message, and
/// method name information.
/// </summary>
public class CapyEventArgs : EventArgs
{
#region Properties #region Properties
/// <summary> /// <summary>
@ -181,43 +197,28 @@ namespace CapyKit
public string MethodName { get; private set; } public string MethodName { get; private set; }
#endregion #endregion
}
#region Constructor /// <summary> Enumeration representing different event level severity values. </summary>
public enum EventLevel
/// <summary> {
/// Initializes a new instance of the CapyEventArgs class with the specified event level, message, and method name.
/// </summary>
/// <param name="level">The severity level of the event.</param>
/// <param name="message">A descriptive message explaining the reason for the event.</param>
/// <param name="method">The name of the method where the event was raised.</param>
public CapyEventArgs(EventLevel level, string message, string method = null)
{
this.Level = level;
this.Message = message;
this.MethodName = method ?? "[Unknown]";
}
#endregion
}
/// <summary> Enumeration representing different event level severity values. </summary>
public enum EventLevel
{
/// <summary> Represents a critical error that requires immediate attention. </summary> /// <summary> Represents a critical error that requires immediate attention. </summary>
[EnumerationDescription("Represents a critical error that requires immediate attention.")] [EnumerationDescription("Represents a critical error that requires immediate attention.")]
Critical = 0, Critical = 0,
/// <summary> Represents an error that prevents the normal execution of the application. </summary> /// <summary> Represents an error that prevents the normal execution of the application. </summary>
[EnumerationDescription("Represents an error that prevents the normal execution of the application.")] [EnumerationDescription("Represents an error that prevents the normal execution of the application.")]
Error = 1, Error = 1,
/// <summary> Represents a warning indicating a non-critical issue that should be addressed. </summary> /// <summary> Represents a warning indicating a non-critical issue that should be addressed. </summary>
[EnumerationDescription("Represents a warning indicating a non-critical issue that should be addressed.")] [EnumerationDescription("Represents a warning indicating a non-critical issue that should be addressed.")]
Warning = 2, Warning = 2,
/// <summary> Represents informational messages that provide useful context to the consumer. </summary> /// <summary> Represents informational messages that provide useful context to the consumer. </summary>
[EnumerationDescription("Represents informational messages that provide useful context to the consumer.")] [EnumerationDescription("Represents informational messages that provide useful context to the consumer.")]
Information = 3, Information = 3,
/// <summary> Represents detailed messages that are typically used for debugging purposes. </summary> /// <summary> Represents detailed messages that are typically used for debugging purposes. </summary>
[EnumerationDescription("Represents detailed messages that are typically used for debugging purposes.")] [EnumerationDescription("Represents detailed messages that are typically used for debugging purposes.")]
Debug = 4 Debug = 4
}
} }

View file

@ -16,6 +16,6 @@
</None> </None>
</ItemGroup> </ItemGroup>
<Import Project="NuGetPublish.targets" Condition="Exists('NuGetPublish.targets')" /> <Import Project="NuGetPublish.targets" Condition="Exists('NuGetPublish.targets')"/>
</Project> </Project>

View file

@ -1,23 +1,11 @@
using System; namespace CapyKit;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CapyKit /// <summary>
/// Holds a value that has been encrypted.
/// </summary>
/// <typeparam name="T"></typeparam>
public class EncryptedValue<T>
{ {
/// <summary>
/// Holds a value that has been encrypted.
/// </summary>
/// <typeparam name="T"></typeparam>
public class EncryptedValue<T>
{
#region Members
//
#endregion
#region Properties #region Properties
/// <summary> /// <summary>
@ -26,5 +14,10 @@ namespace CapyKit
public T Value { get; set; } public T Value { get; set; }
#endregion #endregion
}
#region Members
//
#endregion
} }

View file

@ -1,20 +1,13 @@
using System; namespace CapyKit.Enumerations;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CapyKit.Enumerations /// <summary>
/// An enumeration representing different measurement systems.
/// </summary>
public enum MeasurementSystem
{ {
/// <summary>
/// An enumeration representing different measurement systems.
/// </summary>
public enum MeasurementSystem
{
/// <summary> The imperial measurement system. </summary> /// <summary> The imperial measurement system. </summary>
Imperial = 0, Imperial = 0,
/// <summary> The metric measurement system. </summary> /// <summary> The metric measurement system. </summary>
Metric = 1 Metric = 1
}
} }

View file

@ -1,21 +1,16 @@
using CapyKit.Attributes; using System.Reflection;
using CapyKit.Attributes;
using CapyKit.Helpers; using CapyKit.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace CapyKit.Extensions namespace CapyKit.Extensions;
/// <summary> Provides static extentions methods for providing additional functionality for <see cref="Enum" /> types. </summary>
public static class EnumerationExtensions
{ {
/// <summary> Provides static extentions methods for providing additional functionality for <see cref="Enum"/> types. </summary>
public static class EnumerationExtensions
{
#region Methods #region Methods
/// <summary> /// <summary>
/// A <typeparamref name="T"/> extension method that parses a string into an enumeration. /// A <typeparamref name="T" /> extension method that parses a string into an enumeration.
/// </summary> /// </summary>
/// <typeparam name="T"> Generic type parameter. </typeparam> /// <typeparam name="T"> Generic type parameter. </typeparam>
/// <param name="enumeration"> The enumeration to act on. </param> /// <param name="enumeration"> The enumeration to act on. </param>
@ -27,11 +22,11 @@ namespace CapyKit.Extensions
} }
/// <summary> /// <summary>
/// A <typeparamref name="T"/> extension method that parses a string into an enumeration. /// A <typeparamref name="T" /> extension method that parses a string into an enumeration.
/// </summary> /// </summary>
/// <typeparam name="T"> Generic type parameter. </typeparam> /// <typeparam name="T"> Generic type parameter. </typeparam>
/// <param name="enumeration"> The enumeration to act on. </param> /// <param name="enumeration"> The enumeration to act on. </param>
/// <param name="value"> The string value of the <see cref="Enum"/>. </param> /// <param name="value"> The string value of the <see cref="Enum" />. </param>
/// <param name="ignoreCase"> True to ignore case. </param> /// <param name="ignoreCase"> True to ignore case. </param>
/// <returns> A T. </returns> /// <returns> A T. </returns>
public static T Parse<T>(this T enumeration, string value, bool ignoreCase) where T : Enum public static T Parse<T>(this T enumeration, string value, bool ignoreCase) where T : Enum
@ -40,7 +35,7 @@ namespace CapyKit.Extensions
} }
/// <summary> /// <summary>
/// An <see cref="Enum"/> extension method that gets an integer value representing the enumation. /// An <see cref="Enum" /> extension method that gets an integer value representing the enumation.
/// </summary> /// </summary>
/// <param name="enumeration"> The enumeration to act on. </param> /// <param name="enumeration"> The enumeration to act on. </param>
/// <returns> The integer value of the enumeration. </returns> /// <returns> The integer value of the enumeration. </returns>
@ -49,7 +44,7 @@ namespace CapyKit.Extensions
return (int)Convert.ChangeType(enumeration, TypeCode.Int32); return (int)Convert.ChangeType(enumeration, TypeCode.Int32);
} }
/// <summary> An <see cref="Enum"/> extension method that gets a name. </summary> /// <summary> An <see cref="Enum" /> extension method that gets a name. </summary>
/// <param name="enumeration"> The enumeration to act on. </param> /// <param name="enumeration"> The enumeration to act on. </param>
/// <returns> The name of the enumeration. </returns> /// <returns> The name of the enumeration. </returns>
public static string GetName(this Enum enumeration) public static string GetName(this Enum enumeration)
@ -57,15 +52,15 @@ namespace CapyKit.Extensions
return Enum.GetName(enumeration.GetType(), enumeration) ?? "[Unknown]"; return Enum.GetName(enumeration.GetType(), enumeration) ?? "[Unknown]";
} }
/// <summary> An <see cref="Enum"/> extension method that gets a human readable name. </summary> /// <summary> An <see cref="Enum" /> extension method that gets a human readable name. </summary>
/// <param name="enumeration"> The enumeration to act on. </param> /// <param name="enumeration"> The enumeration to act on. </param>
/// <returns> The human readable name of the enumeration. </returns> /// <returns> The human readable name of the enumeration. </returns>
public static string GetPrettyName(this Enum enumeration) public static string GetPrettyName(this Enum enumeration)
{ {
return LanguageHelper.CamelCaseToHumanReadable(GetName(enumeration)); return LanguageHelper.CamelCaseToHumanReadable(enumeration.GetName());
} }
/// <summary> An <see cref="Enum"/> extension method that gets a description. </summary> /// <summary> An <see cref="Enum" /> extension method that gets a description. </summary>
/// <param name="enumeration"> The enumeration to act on. </param> /// <param name="enumeration"> The enumeration to act on. </param>
/// <returns> /// <returns>
/// The description if available, otherwise the string representation of the enumeration. /// The description if available, otherwise the string representation of the enumeration.
@ -75,10 +70,13 @@ namespace CapyKit.Extensions
var memInfo = enumeration.GetType().GetMember(enumeration.GetName()); var memInfo = enumeration.GetType().GetMember(enumeration.GetName());
if (memInfo.Any()) if (memInfo.Any())
{ {
var attribute = memInfo.First().GetCustomAttribute(typeof(EnumerationDescriptionAttribute)) as EnumerationDescriptionAttribute; var attribute =
memInfo.First().GetCustomAttribute(typeof(EnumerationDescriptionAttribute)) as
EnumerationDescriptionAttribute;
if (attribute == null) if (attribute == null)
{ {
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not find the description for {0}.", args: new[] { enumeration }); CapyEventReporter.EmitEvent(EventLevel.Error, "Could not find the description for {0}.",
args: new[] { enumeration });
} }
else else
{ {
@ -90,5 +88,4 @@ namespace CapyKit.Extensions
} }
#endregion Methods #endregion Methods
}
} }

View file

@ -1,17 +1,15 @@
using System; using System.Linq.Expressions;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
namespace CapyKit.Extensions namespace CapyKit.Extensions;
/// <summary>
/// Provides static extension methods for performing common LINQ operations on <see cref="IEnumerable{T}" /> and
/// <see cref="IQueryable{T}" /> collections.
/// </summary>
public static class LINQExtensions
{ {
/// <summary> Provides static extension methods for performing common LINQ operations on <see cref="IEnumerable{T}"/> and <see cref="IQueryable{T}"/> collections. </summary>
public static class LINQExtensions
{
/// <summary> /// <summary>
/// Filters out items matching a <paramref name="predicate"/> from the collection. /// Filters out items matching a <paramref name="predicate" /> from the collection.
/// </summary> /// </summary>
/// <typeparam name="T"> Generic type parameter. </typeparam> /// <typeparam name="T"> Generic type parameter. </typeparam>
/// <param name="source"> The source to act on. </param> /// <param name="source"> The source to act on. </param>
@ -25,7 +23,7 @@ namespace CapyKit.Extensions
} }
/// <summary> /// <summary>
/// Filters out items matching a <paramref name="predicate"/> from the collection. /// Filters out items matching a <paramref name="predicate" /> from the collection.
/// </summary> /// </summary>
/// <typeparam name="T"> Generic type parameter. </typeparam> /// <typeparam name="T"> Generic type parameter. </typeparam>
/// <param name="source"> The source to act on. </param> /// <param name="source"> The source to act on. </param>
@ -33,11 +31,12 @@ namespace CapyKit.Extensions
/// <returns> /// <returns>
/// An enumerator that allows foreach to be used to process remove in this collection. /// An enumerator that allows foreach to be used to process remove in this collection.
/// </returns> /// </returns>
public static IQueryable<T> Filter<T>(this IQueryable<T> source, System.Linq.Expressions.Expression<Func<T, bool>> predicate) public static IQueryable<T> Filter<T>(this IQueryable<T> source, Expression<Func<T, bool>> predicate)
{ {
if(predicate.Parameters.Count > 1) if (predicate.Parameters.Count > 1)
{ {
CapyEventReporter.EmitEvent(EventLevel.Warning, "More than one parameter was found in the predicate, which could result in unexpected behavior."); CapyEventReporter.EmitEvent(EventLevel.Warning,
"More than one parameter was found in the predicate, which could result in unexpected behavior.");
} }
var parameter = predicate.Parameters.FirstOrDefault(); var parameter = predicate.Parameters.FirstOrDefault();
@ -48,13 +47,13 @@ namespace CapyKit.Extensions
} }
/// <summary> /// <summary>
/// Get a page of items from a collection, skipping <paramref name="pageNumber"/> pages of /// Get a page of items from a collection, skipping <paramref name="pageNumber" /> pages of
/// <paramref name="pageSize"/> items per page. /// <paramref name="pageSize" /> items per page.
/// </summary> /// </summary>
/// <remarks> This method uses natural numbering starting at page 1. </remarks> /// <remarks> This method uses natural numbering starting at page 1. </remarks>
/// <exception cref="ArgumentOutOfRangeException"> /// <exception cref="ArgumentOutOfRangeException">
/// Thrown when <paramref name="pageNumber"/> is less than <c>1</c> or if /// Thrown when <paramref name="pageNumber" /> is less than <c>1</c> or if
/// <paramref name="pageSize"/> is less than /// <paramref name="pageSize" /> is less than
/// <c>1</c>. /// <c>1</c>.
/// </exception> /// </exception>
/// <typeparam name="T"> Generic type parameter. </typeparam> /// <typeparam name="T"> Generic type parameter. </typeparam>
@ -68,7 +67,8 @@ namespace CapyKit.Extensions
{ {
if (pageNumber < 1) if (pageNumber < 1)
{ {
CapyEventReporter.EmitEvent(EventLevel.Error, "pageNumber is out of range. [{0}]", args: new[] { pageNumber }); CapyEventReporter.EmitEvent(EventLevel.Error, "pageNumber is out of range. [{0}]",
args: new[] { pageNumber });
throw new ArgumentOutOfRangeException("pageNumber"); throw new ArgumentOutOfRangeException("pageNumber");
} }
@ -82,13 +82,13 @@ namespace CapyKit.Extensions
} }
/// <summary> /// <summary>
/// Get a page of items from a collection, skipping <paramref name="pageNumber"/> pages of /// Get a page of items from a collection, skipping <paramref name="pageNumber" /> pages of
/// <paramref name="pageSize"/> items per page. /// <paramref name="pageSize" /> items per page.
/// </summary> /// </summary>
/// <remarks> This method uses natural numbering starting at page 1. </remarks> /// <remarks> This method uses natural numbering starting at page 1. </remarks>
/// <exception cref="ArgumentOutOfRangeException"> /// <exception cref="ArgumentOutOfRangeException">
/// Thrown when <paramref name="pageNumber"/> is less than <c>1</c> or if /// Thrown when <paramref name="pageNumber" /> is less than <c>1</c> or if
/// <paramref name="pageSize"/> is less than /// <paramref name="pageSize" /> is less than
/// <c>1</c>. /// <c>1</c>.
/// </exception> /// </exception>
/// <typeparam name="T"> Generic type parameter. </typeparam> /// <typeparam name="T"> Generic type parameter. </typeparam>
@ -102,7 +102,8 @@ namespace CapyKit.Extensions
{ {
if (pageNumber < 1) if (pageNumber < 1)
{ {
CapyEventReporter.EmitEvent(EventLevel.Error, "pageNumber is out of range. [{0}]", args: new[] { pageNumber }); CapyEventReporter.EmitEvent(EventLevel.Error, "pageNumber is out of range. [{0}]",
args: new[] { pageNumber });
throw new ArgumentOutOfRangeException("pageNumber"); throw new ArgumentOutOfRangeException("pageNumber");
} }
@ -116,10 +117,10 @@ namespace CapyKit.Extensions
} }
/// <summary> /// <summary>
/// The number of pages of <paramref name="pageSize"/> size in the given collection. /// The number of pages of <paramref name="pageSize" /> size in the given collection.
/// </summary> /// </summary>
/// <exception cref="ArgumentOutOfRangeException"> /// <exception cref="ArgumentOutOfRangeException">
/// Thrown when <paramref name="pageSize"/> is less than <c>1</c>. /// Thrown when <paramref name="pageSize" /> is less than <c>1</c>.
/// </exception> /// </exception>
/// <typeparam name="T"> Generic type parameter. </typeparam> /// <typeparam name="T"> Generic type parameter. </typeparam>
/// <param name="source"> The source to act on. </param> /// <param name="source"> The source to act on. </param>
@ -138,10 +139,10 @@ namespace CapyKit.Extensions
} }
/// <summary> /// <summary>
/// The number of pages of <paramref name="pageSize"/> size in the given collection. /// The number of pages of <paramref name="pageSize" /> size in the given collection.
/// </summary> /// </summary>
/// <exception cref="ArgumentOutOfRangeException"> /// <exception cref="ArgumentOutOfRangeException">
/// Thrown when <paramref name="pageSize"/> is less than <c>1</c>. /// Thrown when <paramref name="pageSize" /> is less than <c>1</c>.
/// </exception> /// </exception>
/// <typeparam name="T"> Generic type parameter. </typeparam> /// <typeparam name="T"> Generic type parameter. </typeparam>
/// <param name="source"> The source to act on. </param> /// <param name="source"> The source to act on. </param>
@ -171,13 +172,15 @@ namespace CapyKit.Extensions
/// <param name="resultSelector"> The result selector. </param> /// <param name="resultSelector"> The result selector. </param>
/// <param name="defaultGenerator"> (Optional) The default generator. </param> /// <param name="defaultGenerator"> (Optional) The default generator. </param>
/// <returns> An IQueryable&lt;R&gt; </returns> /// <returns> An IQueryable&lt;R&gt; </returns>
public static IQueryable<R> LeftOuterJoin<T, U, TKey, R>(this IQueryable<T> source, IQueryable<U> inner, Expression<Func<T, TKey>> outerSelector, Expression<Func<U, TKey>> innerSelector, Func<T, IEnumerable<U>, R> resultSelector, Func<T, U> defaultGenerator = null) public static IQueryable<R> LeftOuterJoin<T, U, TKey, R>(this IQueryable<T> source, IQueryable<U> inner,
Expression<Func<T, TKey>> outerSelector, Expression<Func<U, TKey>> innerSelector,
Func<T, IEnumerable<U>, R> resultSelector, Func<T, U> defaultGenerator = null)
{ {
Func<T, IEnumerable<U>, R> resultOrDefaultSelector = (i, o) => Func<T, IEnumerable<U>, R> resultOrDefaultSelector = (i, o) =>
{ {
if (defaultGenerator == null) if (defaultGenerator == null)
{ {
defaultGenerator = (t) => default(U); defaultGenerator = t => default;
} }
if (!o.Any()) if (!o.Any())
@ -202,7 +205,9 @@ namespace CapyKit.Extensions
/// <param name="innerSelector"> The inner selector. </param> /// <param name="innerSelector"> The inner selector. </param>
/// <param name="resultSelector"> The result selector. </param> /// <param name="resultSelector"> The result selector. </param>
/// <returns> An IQueryable&lt;R&gt; </returns> /// <returns> An IQueryable&lt;R&gt; </returns>
private static IQueryable<R> LeftOuterJoin<T, U, TKey, R>(this IQueryable<T> source, IQueryable<U> inner, Expression<Func<T, TKey>> outerSelector, Expression<Func<U, TKey>> innerSelector, Expression<Func<T, IEnumerable<U>, R>> resultSelector) private static IQueryable<R> LeftOuterJoin<T, U, TKey, R>(this IQueryable<T> source, IQueryable<U> inner,
Expression<Func<T, TKey>> outerSelector, Expression<Func<U, TKey>> innerSelector,
Expression<Func<T, IEnumerable<U>, R>> resultSelector)
{ {
return source.GroupJoin(inner, outerSelector, innerSelector, resultSelector); return source.GroupJoin(inner, outerSelector, innerSelector, resultSelector);
} }
@ -221,13 +226,15 @@ namespace CapyKit.Extensions
/// <returns> /// <returns>
/// An enumerator that allows foreach to be used to process left outter join in this collection. /// An enumerator that allows foreach to be used to process left outter join in this collection.
/// </returns> /// </returns>
public static IEnumerable<R> LeftOuterJoin<T, U, TKey, R>(this IEnumerable<T> source, IEnumerable<U> inner, Func<T, TKey> outerSelector, Func<U, TKey> innerSelector, Func<T, IEnumerable<U>, R> resultSelector, Func<T, U> defaultGenerator = null) public static IEnumerable<R> LeftOuterJoin<T, U, TKey, R>(this IEnumerable<T> source, IEnumerable<U> inner,
Func<T, TKey> outerSelector, Func<U, TKey> innerSelector, Func<T, IEnumerable<U>, R> resultSelector,
Func<T, U> defaultGenerator = null)
{ {
var combined = source.GroupJoin(inner, outerSelector, innerSelector, (i, o) => new { inner = i, outer = o }); var combined = source.GroupJoin(inner, outerSelector, innerSelector, (i, o) => new { inner = i, outer = o });
if (defaultGenerator == null) if (defaultGenerator == null)
{ {
defaultGenerator = (t) => default(U); defaultGenerator = t => default;
} }
return combined.Select(anon => return combined.Select(anon =>
@ -242,7 +249,7 @@ namespace CapyKit.Extensions
} }
/// <summary> /// <summary>
/// Enumerates distinct items in this collection as defined by the key <paramref name="property"/>. /// Enumerates distinct items in this collection as defined by the key <paramref name="property" />.
/// </summary> /// </summary>
/// <typeparam name="T"> Generic type parameter of the parent object. </typeparam> /// <typeparam name="T"> Generic type parameter of the parent object. </typeparam>
/// <typeparam name="U"> Generic type parameter property value. </typeparam> /// <typeparam name="U"> Generic type parameter property value. </typeparam>
@ -256,5 +263,4 @@ namespace CapyKit.Extensions
var propertyComparer = new PropertyComparer<T, U>(property); var propertyComparer = new PropertyComparer<T, U>(property);
return items.Distinct(propertyComparer); return items.Distinct(propertyComparer);
} }
}
} }

View file

@ -1,17 +1,11 @@
using System; namespace CapyKit.Extensions;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CapyKit.Extensions /// <summary> An class containing extenstions that apply to any object type. </summary>
public static class ObjectExtensions
{ {
/// <summary> An class containing extenstions that apply to any object type. </summary>
public static class ObjectExtensions
{
/// <summary> /// <summary>
/// An object extension method that updates the properties of a given <paramref name="target"/> /// An object extension method that updates the properties of a given <paramref name="target" />
/// object with the values from a given <paramref name="source"/> object. /// object with the values from a given <paramref name="source" /> object.
/// </summary> /// </summary>
/// <typeparam name="T"> Generic type parameter. </typeparam> /// <typeparam name="T"> Generic type parameter. </typeparam>
/// <param name="target"> The target object to act on. </param> /// <param name="target"> The target object to act on. </param>
@ -29,8 +23,8 @@ namespace CapyKit.Extensions
} }
/// <summary> /// <summary>
/// An object extension method that updates the properties of a given <paramref name="target"/> /// An object extension method that updates the properties of a given <paramref name="target" />
/// object with the values from a given <paramref name="source"/> object. /// object with the values from a given <paramref name="source" /> object.
/// </summary> /// </summary>
/// <param name="target"> The target object to act on. </param> /// <param name="target"> The target object to act on. </param>
/// <param name="source"> Source for the new property values. </param> /// <param name="source"> Source for the new property values. </param>
@ -38,15 +32,16 @@ namespace CapyKit.Extensions
{ {
var targetProperties = target.GetType().GetProperties(); var targetProperties = target.GetType().GetProperties();
var sourceProperties = source.GetType().GetProperties(); var sourceProperties = source.GetType().GetProperties();
var matchingProperties = targetProperties.Join(sourceProperties, outer => new { outer.Name, outer.PropertyType }, inner => new { inner.Name, inner.PropertyType }, (outer, inner) => new { Target = outer, Source = inner }); var matchingProperties = targetProperties.Join(sourceProperties,
outer => new { outer.Name, outer.PropertyType }, inner => new { inner.Name, inner.PropertyType },
(outer, inner) => new { Target = outer, Source = inner });
foreach (var propertyMatch in matchingProperties) foreach (var propertyMatch in matchingProperties)
{ {
if(propertyMatch.Target.CanWrite) if (propertyMatch.Target.CanWrite)
{ {
propertyMatch.Target.SetValue(target, propertyMatch.Source.GetValue(source)); propertyMatch.Target.SetValue(target, propertyMatch.Source.GetValue(source));
} }
} }
} }
}
} }

View file

@ -1,14 +1,8 @@
using System; namespace CapyKit.Extensions;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CapyKit.Extensions /// <summary> Provides static extentions methods for providing additional functionality for <see cref="string" /> types. </summary>
public static class StringExtensions
{ {
/// <summary> Provides static extentions methods for providing additional functionality for <see cref="string"/> types. </summary>
public static class StringExtensions
{
#region Members #region Members
// //
@ -23,7 +17,7 @@ namespace CapyKit.Extensions
/// <returns> /// <returns>
/// The original string if not null or empty, otherwise the replacement string. /// The original string if not null or empty, otherwise the replacement string.
/// </returns> /// </returns>
/// <seealso cref="string.IsNullOrEmpty(string?)"/> /// <seealso cref="string.IsNullOrEmpty(string?)" />
public static string IfNullOrEmpty(this string value, string replacement) public static string IfNullOrEmpty(this string value, string replacement)
{ {
if (string.IsNullOrEmpty(value)) if (string.IsNullOrEmpty(value))
@ -40,7 +34,7 @@ namespace CapyKit.Extensions
/// <returns> /// <returns>
/// The original string if not null or whitespace, otherwise the replacement string. /// The original string if not null or whitespace, otherwise the replacement string.
/// </returns> /// </returns>
/// <seealso cref="string.IsNullOrWhiteSpace(string?)"/> /// <seealso cref="string.IsNullOrWhiteSpace(string?)" />
public static string IfNullOrWhiteSpace(this string value, string replacement) public static string IfNullOrWhiteSpace(this string value, string replacement)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
@ -52,5 +46,4 @@ namespace CapyKit.Extensions
} }
#endregion Methods #endregion Methods
}
} }

View file

@ -1,16 +1,12 @@
using CapyKit.Enumerations; using System.Security.Cryptography;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Threading.Tasks; using CapyKit.Enumerations;
namespace CapyKit.Helpers namespace CapyKit.Helpers;
/// <summary> Static class providing helper methods for various calculations. </summary>
public static class CalculationHelper
{ {
/// <summary> Static class providing helper methods for various calculations. </summary>
public static class CalculationHelper
{
#region Members #region Members
/// <summary> The earth's radius in kilometers. </summary> /// <summary> The earth's radius in kilometers. </summary>
@ -27,7 +23,7 @@ namespace CapyKit.Helpers
#region Methods #region Methods
/// <summary> /// <summary>
/// Calculates the hash of a given string using an <see cref="MD5"/> value as the first 32 bits. /// Calculates the hash of a given string using an <see cref="MD5" /> value as the first 32 bits.
/// </summary> /// </summary>
/// <param name="str"> The string to be hashed. </param> /// <param name="str"> The string to be hashed. </param>
/// <returns> The calculated hash. </returns> /// <returns> The calculated hash. </returns>
@ -37,7 +33,7 @@ namespace CapyKit.Helpers
/// </remarks> /// </remarks>
public static int CalculateHash(string str) public static int CalculateHash(string str)
{ {
MD5 md5Hasher = MD5.Create(); var md5Hasher = MD5.Create();
var md5Hash = md5Hasher.ComputeHash(Encoding.UTF8.GetBytes(str)); var md5Hash = md5Hasher.ComputeHash(Encoding.UTF8.GetBytes(str));
var hash = BitConverter.ToInt32(md5Hash, 0); var hash = BitConverter.ToInt32(md5Hash, 0);
@ -49,16 +45,16 @@ namespace CapyKit.Helpers
/// <returns> The calculated 16 character hexadecimal hash. </returns> /// <returns> The calculated 16 character hexadecimal hash. </returns>
public static string CalculateHexHash(string str) public static string CalculateHexHash(string str)
{ {
byte[] bytes = Encoding.UTF8.GetBytes(str); var bytes = Encoding.UTF8.GetBytes(str);
MD5 md5Hasher = MD5.Create(); var md5Hasher = MD5.Create();
byte[] hash = md5Hasher.ComputeHash(bytes); var hash = md5Hasher.ComputeHash(bytes);
char[] hash2 = new char[16]; var hash2 = new char[16];
// Note that here we are wasting bits of hash! // Note that here we are wasting bits of hash!
// But it isn't really important, because hash.Length == 32 // But it isn't really important, because hash.Length == 32
for (int i = 0; i < hash2.Length; i++) for (var i = 0; i < hash2.Length; i++)
{ {
hash2[i] = CalculationHelper.chars[hash[i] % CalculationHelper.chars.Length]; hash2[i] = CalculationHelper.chars[hash[i] % CalculationHelper.chars.Length];
} }
@ -79,15 +75,17 @@ namespace CapyKit.Helpers
/// Uses the <a href="https://www.movable-type.co.uk/scripts/latlong.html">haversine</a> formula /// Uses the <a href="https://www.movable-type.co.uk/scripts/latlong.html">haversine</a> formula
/// to calculate the "as-the-crow-flies" distance between two points on earth. /// to calculate the "as-the-crow-flies" distance between two points on earth.
/// </remarks> /// </remarks>
/// <seealso cref="GetDistance(double, double, double, double, MeasurementSystem)"/> /// <seealso cref="GetDistance(double, double, double, double, MeasurementSystem)" />
public static decimal GetDistance(decimal latitudeOrigin, decimal longitudeOrigin, decimal latitudeDestination, decimal longitudeDestination, MeasurementSystem measurementSystem = MeasurementSystem.Imperial) public static decimal GetDistance(decimal latitudeOrigin, decimal longitudeOrigin, decimal latitudeDestination,
decimal longitudeDestination, MeasurementSystem measurementSystem = MeasurementSystem.Imperial)
{ {
double latitudeOriginalDouble = Convert.ToDouble(latitudeOrigin); var latitudeOriginalDouble = Convert.ToDouble(latitudeOrigin);
double longitudeOriginDouble = Convert.ToDouble(longitudeOrigin); var longitudeOriginDouble = Convert.ToDouble(longitudeOrigin);
double latitudeDestinationDouble = Convert.ToDouble(latitudeDestination); var latitudeDestinationDouble = Convert.ToDouble(latitudeDestination);
double longitudeDestinationDouble = Convert.ToDouble(longitudeDestination); var longitudeDestinationDouble = Convert.ToDouble(longitudeDestination);
var result = GetDistance(latitudeOriginalDouble, longitudeOriginDouble, latitudeDestinationDouble, longitudeDestinationDouble, measurementSystem); var result = GetDistance(latitudeOriginalDouble, longitudeOriginDouble, latitudeDestinationDouble,
longitudeDestinationDouble, measurementSystem);
return Convert.ToDecimal(result); return Convert.ToDecimal(result);
} }
@ -103,17 +101,19 @@ namespace CapyKit.Helpers
/// Uses the <a href="https://www.movable-type.co.uk/scripts/latlong.html">haversine</a> formula /// Uses the <a href="https://www.movable-type.co.uk/scripts/latlong.html">haversine</a> formula
/// to calculate the "as-the-crow-flies" distance between two points on earth. /// to calculate the "as-the-crow-flies" distance between two points on earth.
/// </remarks> /// </remarks>
public static double GetDistance(double latitudeOrigin, double longitudeOrigin, double latitudeDestination, double longitudeDestination, MeasurementSystem measurementSystem = MeasurementSystem.Imperial) public static double GetDistance(double latitudeOrigin, double longitudeOrigin, double latitudeDestination,
double longitudeDestination, MeasurementSystem measurementSystem = MeasurementSystem.Imperial)
{ {
var thetaLatitude = DegreesToRadians(latitudeOrigin); var thetaLatitude = DegreesToRadians(latitudeOrigin);
var thetaLongitude = DegreesToRadians(longitudeOrigin); var thetaLongitude = DegreesToRadians(longitudeOrigin);
var deltaLatitude = DegreesToRadians(latitudeDestination - latitudeOrigin); var deltaLatitude = DegreesToRadians(latitudeDestination - latitudeOrigin);
var deltaLongitude = DegreesToRadians(longitudeDestination - longitudeOrigin); var deltaLongitude = DegreesToRadians(longitudeDestination - longitudeOrigin);
var haversineTheta = Math.Sin(deltaLatitude / 2) * Math.Sin(deltaLatitude / 2) + Math.Cos(thetaLatitude) * Math.Cos(thetaLongitude) * Math.Sin(deltaLongitude / 2) * Math.Sin(deltaLongitude / 2); var haversineTheta = Math.Sin(deltaLatitude / 2) * Math.Sin(deltaLatitude / 2) + Math.Cos(thetaLatitude) *
Math.Cos(thetaLongitude) * Math.Sin(deltaLongitude / 2) * Math.Sin(deltaLongitude / 2);
var angularDistance = 2 * Math.Atan2(Math.Sqrt(haversineTheta), Math.Sqrt(1 - haversineTheta)); var angularDistance = 2 * Math.Atan2(Math.Sqrt(haversineTheta), Math.Sqrt(1 - haversineTheta));
var distance = EARTH_RADIUS_KILOMETERS * angularDistance; var distance = CalculationHelper.EARTH_RADIUS_KILOMETERS * angularDistance;
if (measurementSystem == MeasurementSystem.Imperial) if (measurementSystem == MeasurementSystem.Imperial)
{ {
@ -128,7 +128,7 @@ namespace CapyKit.Helpers
/// <returns> The value in miles. </returns> /// <returns> The value in miles. </returns>
public static double KilometersToMiles(double kilometers) public static double KilometersToMiles(double kilometers)
{ {
return kilometers * MILES_PER_KILOMETER; return kilometers * CalculationHelper.MILES_PER_KILOMETER;
} }
/// <summary> Converts miles to kilometers. </summary> /// <summary> Converts miles to kilometers. </summary>
@ -136,7 +136,7 @@ namespace CapyKit.Helpers
/// <returns> The value in kilometers. </returns> /// <returns> The value in kilometers. </returns>
public static double MilesToKilometers(double miles) public static double MilesToKilometers(double miles)
{ {
return miles / MILES_PER_KILOMETER; return miles / CalculationHelper.MILES_PER_KILOMETER;
} }
/// <summary> Convers degrees to radians. </summary> /// <summary> Convers degrees to radians. </summary>
@ -156,5 +156,4 @@ namespace CapyKit.Helpers
} }
#endregion Methods #endregion Methods
}
} }

View file

@ -1,15 +1,10 @@
using System; using System.IO.Compression;
using System.Collections.Generic;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CapyKit.Helpers namespace CapyKit.Helpers;
/// <summary> A class that contains methods for managing data compression. </summary>
public static class CompressionHelper
{ {
/// <summary> A class that contains methods for managing data compression. </summary>
public static class CompressionHelper
{
#region Members #region Members
// //
@ -36,6 +31,7 @@ namespace CapyKit.Helpers
inputStream.CopyTo(gzipStream); inputStream.CopyTo(gzipStream);
gzipStream.Flush(); gzipStream.Flush();
} }
return outputStream.ToArray(); return outputStream.ToArray();
} }
} }
@ -58,7 +54,7 @@ namespace CapyKit.Helpers
/// <summary> Decompresses a given <c>base64</c> encoded string of <c>gzip</c> format. </summary> /// <summary> Decompresses a given <c>base64</c> encoded string of <c>gzip</c> format. </summary>
/// <typeparam name="T"> Generic type parameter. </typeparam> /// <typeparam name="T"> Generic type parameter. </typeparam>
/// <param name="encodedString"> The <c>base64</c> encoded <c>gzip</c> string. </param> /// <param name="encodedString"> The <c>base64</c> encoded <c>gzip</c> string. </param>
/// <returns> A <typeparamref name="T"/> typed object. </returns> /// <returns> A <typeparamref name="T" /> typed object. </returns>
public static T Decompress<T>(string encodedString) public static T Decompress<T>(string encodedString)
{ {
var bytes = Convert.FromBase64String(encodedString); var bytes = Convert.FromBase64String(encodedString);
@ -68,7 +64,7 @@ namespace CapyKit.Helpers
/// <summary> Decompresses a given compressed <c>gzip</c> byte stream. </summary> /// <summary> Decompresses a given compressed <c>gzip</c> byte stream. </summary>
/// <typeparam name="T"> Generic type parameter. </typeparam> /// <typeparam name="T"> Generic type parameter. </typeparam>
/// <param name="byteStream"> The compressed byte stream. </param> /// <param name="byteStream"> The compressed byte stream. </param>
/// <returns> A <typeparamref name="T"/> typed object. </returns> /// <returns> A <typeparamref name="T" /> typed object. </returns>
public static T Decompress<T>(byte[] byteStream) public static T Decompress<T>(byte[] byteStream)
{ {
try try
@ -80,6 +76,7 @@ namespace CapyKit.Helpers
{ {
gzipStream.CopyTo(outputStream); gzipStream.CopyTo(outputStream);
} }
var bytes = outputStream.ToArray(); var bytes = outputStream.ToArray();
return SerializationHelper.Deserialize<T>(bytes); return SerializationHelper.Deserialize<T>(bytes);
} }
@ -102,5 +99,4 @@ namespace CapyKit.Helpers
} }
#endregion Methods #endregion Methods
}
} }

View file

@ -1,14 +1,7 @@
using System; namespace CapyKit.Helpers;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices.ObjectiveC;
using System.Text;
using System.Threading.Tasks;
namespace CapyKit.Helpers public class EncryptionHelper
{ {
public class EncryptionHelper
{
#region Members #region Members
private string encryptionKey; private string encryptionKey;
@ -23,10 +16,10 @@ namespace CapyKit.Helpers
} }
#endregion #endregion
} }
public interface IEncryptionAlgorithm public interface IEncryptionAlgorithm
{ {
#region Properties #region Properties
string AlgorithmName { get; } string AlgorithmName { get; }
@ -40,5 +33,4 @@ namespace CapyKit.Helpers
T Decrypt<T>(EncryptedValue<T> encryptedValue, params object[] args); T Decrypt<T>(EncryptedValue<T> encryptedValue, params object[] args);
#endregion #endregion
}
} }

View file

@ -2,7 +2,7 @@ using CapyKit.Extensions;
namespace CapyKit.Helpers; namespace CapyKit.Helpers;
/// <summary> A class that contains methods for managing <see cref="Enum"/> objects. </summary> /// <summary> A class that contains methods for managing <see cref="Enum" /> objects. </summary>
public static class EnumerationHelper public static class EnumerationHelper
{ {
#region Methods #region Methods
@ -18,7 +18,7 @@ public static class EnumerationHelper
return (T)(object)value; return (T)(object)value;
} }
return default(T); return default;
} }
/// <summary> Gets enumeration value from a string value. </summary> /// <summary> Gets enumeration value from a string value. </summary>
@ -39,7 +39,7 @@ public static class EnumerationHelper
} }
} }
return default(T); return default;
} }
/// <summary> /// <summary>

View file

@ -1,23 +1,20 @@
using System; using System.Security.Cryptography;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Net;
namespace CapyKit.Helpers namespace CapyKit.Helpers;
/// <summary> A class that contains methods for managing key creation and validation against a master key. </summary>
public class KeyHelper
{ {
/// <summary> A class that contains methods for managing key creation and validation against a master key. </summary>
public class KeyHelper
{
/// <summary> The master key accessor function. </summary> /// <summary> The master key accessor function. </summary>
private Func<byte[]> masterKeyAccessor; private Func<byte[]> masterKeyAccessor;
/// <summary> The salt size accessor function. </summary>
private Func<int> saltSizeAccessor;
/// <summary> Number of parts accessor function. </summary> /// <summary> Number of parts accessor function. </summary>
private Func<int> numPartsAccessor; private Func<int> numPartsAccessor;
/// <summary> The salt size accessor function. </summary>
private Func<int> saltSizeAccessor;
/// <summary> Sets the master key. </summary> /// <summary> Sets the master key. </summary>
/// <param name="accessor"> The accessor function. </param> /// <param name="accessor"> The accessor function. </param>
public void SetMasterKeyAccessor(Func<byte[]> accessor) public void SetMasterKeyAccessor(Func<byte[]> accessor)
@ -38,6 +35,7 @@ namespace CapyKit.Helpers
CapyEventReporter.EmitEvent(EventLevel.Error, errorMessage); CapyEventReporter.EmitEvent(EventLevel.Error, errorMessage);
throw new InvalidOperationException(errorMessage); throw new InvalidOperationException(errorMessage);
} }
return this.masterKeyAccessor(); return this.masterKeyAccessor();
} }
@ -69,13 +67,14 @@ namespace CapyKit.Helpers
/// <returns> The number parts. </returns> /// <returns> The number parts. </returns>
public int GetNumParts() public int GetNumParts()
{ {
int parts = this.numPartsAccessor != null ? this.numPartsAccessor() : 2; var parts = this.numPartsAccessor != null ? this.numPartsAccessor() : 2;
if (parts < 2) if (parts < 2)
{ {
var errorMessage = "Number of parts must be 2 or more."; var errorMessage = "Number of parts must be 2 or more.";
CapyEventReporter.EmitEvent(EventLevel.Error, errorMessage); CapyEventReporter.EmitEvent(EventLevel.Error, errorMessage);
throw new ArgumentException(errorMessage); throw new ArgumentException(errorMessage);
} }
return parts; return parts;
} }
@ -87,12 +86,12 @@ namespace CapyKit.Helpers
/// <returns> The calculated signature. </returns> /// <returns> The calculated signature. </returns>
private byte[] ComputeSignature(byte[] salt) private byte[] ComputeSignature(byte[] salt)
{ {
byte[] masterKey = GetMasterKey(); var masterKey = GetMasterKey();
using (var hmac = new HMACSHA256(masterKey)) using (var hmac = new HMACSHA256(masterKey))
{ {
byte[] hash = hmac.ComputeHash(salt); var hash = hmac.ComputeHash(salt);
int sigLength = salt.Length; var sigLength = salt.Length;
byte[] signature = new byte[sigLength]; var signature = new byte[sigLength];
Array.Copy(hash, signature, sigLength); Array.Copy(hash, signature, sigLength);
return signature; return signature;
} }
@ -103,11 +102,12 @@ namespace CapyKit.Helpers
/// <returns> A string. </returns> /// <returns> A string. </returns>
private string BytesToHex(byte[] bytes) private string BytesToHex(byte[] bytes)
{ {
StringBuilder sb = new StringBuilder(bytes.Length * 2); var sb = new StringBuilder(bytes.Length * 2);
foreach (var b in bytes) foreach (var b in bytes)
{ {
sb.Append(b.ToString("X2")); sb.Append(b.ToString("X2"));
} }
return sb.ToString(); return sb.ToString();
} }
@ -125,11 +125,13 @@ namespace CapyKit.Helpers
CapyEventReporter.EmitEvent(EventLevel.Error, errorMessage); CapyEventReporter.EmitEvent(EventLevel.Error, errorMessage);
throw new ArgumentException(errorMessage); throw new ArgumentException(errorMessage);
} }
byte[] bytes = new byte[hex.Length / 2];
for (int i = 0; i < hex.Length; i += 2) var bytes = new byte[hex.Length / 2];
for (var i = 0; i < hex.Length; i += 2)
{ {
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16); bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
} }
return bytes; return bytes;
} }
@ -140,22 +142,25 @@ namespace CapyKit.Helpers
/// <returns> The formatted key. </returns> /// <returns> The formatted key. </returns>
private string FormatKey(string hex) private string FormatKey(string hex)
{ {
int parts = GetNumParts(); var parts = GetNumParts();
int totalLength = hex.Length; var totalLength = hex.Length;
int baseLength = totalLength / parts; var baseLength = totalLength / parts;
int remainder = totalLength % parts; var remainder = totalLength % parts;
StringBuilder formatted = new StringBuilder(); var formatted = new StringBuilder();
int currentIndex = 0; var currentIndex = 0;
for (int i = 0; i < parts; i++) for (var i = 0; i < parts; i++)
{ {
// Distribute any extra characters across the first few groups. // Distribute any extra characters across the first few groups.
int groupLength = baseLength + (i < remainder ? 1 : 0); var groupLength = baseLength + (i < remainder ? 1 : 0);
formatted.Append(hex.Substring(currentIndex, groupLength)); formatted.Append(hex.Substring(currentIndex, groupLength));
currentIndex += groupLength; currentIndex += groupLength;
if (i < parts - 1) if (i < parts - 1)
{
formatted.Append("-"); formatted.Append("-");
} }
}
return formatted.ToString(); return formatted.ToString();
} }
@ -163,40 +168,44 @@ namespace CapyKit.Helpers
/// <returns> The key. </returns> /// <returns> The key. </returns>
public string GenerateKey() public string GenerateKey()
{ {
int saltSize = GetSaltSize(); var saltSize = GetSaltSize();
byte[] salt = new byte[saltSize]; var salt = new byte[saltSize];
using (var rng = RandomNumberGenerator.Create()) using (var rng = RandomNumberGenerator.Create())
{ {
rng.GetBytes(salt); rng.GetBytes(salt);
} }
byte[] signature = ComputeSignature(salt); var signature = ComputeSignature(salt);
string saltHex = BytesToHex(salt); var saltHex = BytesToHex(salt);
string signatureHex = BytesToHex(signature); var signatureHex = BytesToHex(signature);
string combinedHex = saltHex + signatureHex; var combinedHex = saltHex + signatureHex;
return FormatKey(combinedHex); return FormatKey(combinedHex);
} }
/// <summary> Validates the provided key. </summary> /// <summary> Validates the provided key. </summary>
/// <param name="providedKey"> The provided key. </param> /// <param name="providedKey"> The provided key. </param>
/// <returns> True if it succeeds, false if it fails. </returns> /// <returns> True if it succeeds, false if it fails. </returns>
/// <seealso cref="GetMasterKey"/> /// <seealso cref="GetMasterKey" />
/// <seealso cref="SetMasterKeyAccessor(Func{byte[]})"/> /// <seealso cref="SetMasterKeyAccessor(Func{byte[]})" />
/// <seealso cref="masterKeyAccessor"/> /// <seealso cref="masterKeyAccessor" />
public bool ValidateKey(string providedKey) public bool ValidateKey(string providedKey)
{ {
if (string.IsNullOrWhiteSpace(providedKey)) if (string.IsNullOrWhiteSpace(providedKey))
{
return false; return false;
}
// Remove dashes. // Remove dashes.
string cleanedKey = providedKey.Replace("-", ""); var cleanedKey = providedKey.Replace("-", "");
int saltSize = GetSaltSize(); var saltSize = GetSaltSize();
int expectedTotalHexLength = 4 * saltSize; // salt (2*saltSize) + signature (2*saltSize) var expectedTotalHexLength = 4 * saltSize; // salt (2*saltSize) + signature (2*saltSize)
if (cleanedKey.Length != expectedTotalHexLength) if (cleanedKey.Length != expectedTotalHexLength)
{
return false; return false;
}
string saltHex = cleanedKey.Substring(0, 2 * saltSize); var saltHex = cleanedKey.Substring(0, 2 * saltSize);
string signatureHex = cleanedKey.Substring(2 * saltSize); var signatureHex = cleanedKey.Substring(2 * saltSize);
byte[] salt; byte[] salt;
byte[] providedSignature; byte[] providedSignature;
@ -210,16 +219,20 @@ namespace CapyKit.Helpers
return false; return false;
} }
byte[] expectedSignature = ComputeSignature(salt); var expectedSignature = ComputeSignature(salt);
if (expectedSignature.Length != providedSignature.Length) if (expectedSignature.Length != providedSignature.Length)
{
return false; return false;
}
for (int i = 0; i < expectedSignature.Length; i++) for (var i = 0; i < expectedSignature.Length; i++)
{ {
if (expectedSignature[i] != providedSignature[i]) if (expectedSignature[i] != providedSignature[i])
{
return false; return false;
} }
}
return true; return true;
} }
}
} }

View file

@ -1,17 +1,12 @@
using System; using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace CapyKit.Helpers namespace CapyKit.Helpers;
/// <summary>
/// Helper class for handling text transformations.
/// </summary>
public class LanguageHelper
{ {
/// <summary>
/// Helper class for handling text transformations.
/// </summary>
public class LanguageHelper
{
#region Methods #region Methods
/// <summary> Converts camel case text to human readable text. </summary> /// <summary> Converts camel case text to human readable text. </summary>
@ -23,11 +18,11 @@ namespace CapyKit.Helpers
/// <returns> A string in human readable format. </returns> /// <returns> A string in human readable format. </returns>
public static string CamelCaseToHumanReadable(string value) public static string CamelCaseToHumanReadable(string value)
{ {
var regex = new Regex(@"(?<=[A-Z])(?=[A-Z][a-z]) | (?<=[^A-Z])(?=[A-Z]) | (?<=[A-Za-z])(?=[^A-Za-z])", RegexOptions.IgnorePatternWhitespace); var regex = new Regex(@"(?<=[A-Z])(?=[A-Z][a-z]) | (?<=[^A-Z])(?=[A-Z]) | (?<=[A-Za-z])(?=[^A-Za-z])",
RegexOptions.IgnorePatternWhitespace);
return regex.Replace(value, " "); return regex.Replace(value, " ");
} }
#endregion #endregion
}
} }

View file

@ -9,8 +9,8 @@ namespace CapyKit.Helpers;
public static class PropertyHelper<T> public static class PropertyHelper<T>
{ {
/// <summary> /// <summary>
/// Gets the reflected <typeparamref name="TProperty"/> value of an instanced /// Gets the reflected <typeparamref name="TProperty" /> value of an instanced
/// <typeparamref name="T"/> object. /// <typeparamref name="T" /> object.
/// </summary> /// </summary>
/// <typeparam name="TProperty"> Type of the property. </typeparam> /// <typeparam name="TProperty"> Type of the property. </typeparam>
/// <param name="obj"> The instanced object. </param> /// <param name="obj"> The instanced object. </param>
@ -22,12 +22,12 @@ public static class PropertyHelper<T>
} }
/// <summary> /// <summary>
/// Gets the reflected <see cref="PropertyInfo"/> of an instanced <typeparamref name="T"/> /// Gets the reflected <see cref="PropertyInfo" /> of an instanced <typeparamref name="T" />
/// object. /// object.
/// </summary> /// </summary>
/// <param name="obj"> The instanced object. </param> /// <param name="obj"> The instanced object. </param>
/// <param name="propertyName"> Name of the property. </param> /// <param name="propertyName"> Name of the property. </param>
/// <returns> The reflected <see cref="PropertyInfo"/>. </returns> /// <returns> The reflected <see cref="PropertyInfo" />. </returns>
public static PropertyInfo GetProperty(T obj, string propertyName) public static PropertyInfo GetProperty(T obj, string propertyName)
{ {
if (obj == null) if (obj == null)
@ -40,7 +40,10 @@ public static class PropertyHelper<T>
.FirstOrDefault(); .FirstOrDefault();
} }
/// <summary> Gets the reflected <typeparamref name="TProperty"/> value of an instanced <typeparamref name="T"/> object property value as described by a <paramref name="selector"/>. </summary> /// <summary>
/// Gets the reflected <typeparamref name="TProperty" /> value of an instanced <typeparamref name="T" /> object
/// property value as described by a <paramref name="selector" />.
/// </summary>
/// <typeparam name="TProperty"> Type of the property. </typeparam> /// <typeparam name="TProperty"> Type of the property. </typeparam>
/// <param name="obj"> The instanced object. </param> /// <param name="obj"> The instanced object. </param>
/// <param name="selector"> The property selector function that identifies the property. </param> /// <param name="selector"> The property selector function that identifies the property. </param>
@ -51,16 +54,18 @@ public static class PropertyHelper<T>
} }
/// <summary> /// <summary>
/// Gets the reflected <see cref="PropertyInfo"/> of an <typeparamref name="T"/> object as /// Gets the reflected <see cref="PropertyInfo" /> of an <typeparamref name="T" /> object as
/// described by a <paramref name="selector"/>. /// described by a <paramref name="selector" />.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException"> Thrown when the <paramref name="selector"/> does /// <exception cref="InvalidOperationException">
/// not return a <see cref="LambdaExpression.Body"/> /// Thrown when the <paramref name="selector" /> does
/// not return a <see cref="LambdaExpression.Body" />
/// of type /// of type
/// <see cref="ExpressionType.MemberAccess"/>. </exception> /// <see cref="ExpressionType.MemberAccess" />.
/// </exception>
/// <typeparam name="TProperty"> Type of the property. </typeparam> /// <typeparam name="TProperty"> Type of the property. </typeparam>
/// <param name="selector"> The property selector function that identifies the property. </param> /// <param name="selector"> The property selector function that identifies the property. </param>
/// <returns> The reflected <see cref="PropertyInfo"/>. </returns> /// <returns> The reflected <see cref="PropertyInfo" />. </returns>
public static PropertyInfo GetProperty<TProperty>(Expression<Func<T, TProperty>> selector) public static PropertyInfo GetProperty<TProperty>(Expression<Func<T, TProperty>> selector)
{ {
Expression body = selector; Expression body = selector;
@ -82,7 +87,7 @@ public static class PropertyHelper<T>
} }
/// <summary> /// <summary>
/// Gets an <see cref="EnumerationDescriptionAttribute"/> attribute from a given property. /// Gets an <see cref="EnumerationDescriptionAttribute" /> attribute from a given property.
/// </summary> /// </summary>
/// <typeparam name="TProperty"> Type of the property. </typeparam> /// <typeparam name="TProperty"> Type of the property. </typeparam>
/// <param name="selector"> The property selector function that identifies the property. </param> /// <param name="selector"> The property selector function that identifies the property. </param>
@ -94,7 +99,7 @@ public static class PropertyHelper<T>
} }
/// <summary> /// <summary>
/// Gets a <see cref="ValueFormatAttribute"/> attribute from the given property. /// Gets a <see cref="ValueFormatAttribute" /> attribute from the given property.
/// </summary> /// </summary>
/// <typeparam name="TProperty"> Type of the property. </typeparam> /// <typeparam name="TProperty"> Type of the property. </typeparam>
/// <param name="selector"> The property selector function that identifies the property. </param> /// <param name="selector"> The property selector function that identifies the property. </param>
@ -105,7 +110,7 @@ public static class PropertyHelper<T>
} }
/// <summary> /// <summary>
/// Gets a <see cref="ParameterAttribute"/> attribute from the given property. /// Gets a <see cref="ParameterAttribute" /> attribute from the given property.
/// </summary> /// </summary>
/// <typeparam name="TProperty"> Type of the property. </typeparam> /// <typeparam name="TProperty"> Type of the property. </typeparam>
/// <param name="selector"> The property selector function that identifies the property. </param> /// <param name="selector"> The property selector function that identifies the property. </param>
@ -115,11 +120,11 @@ public static class PropertyHelper<T>
return GetPropertyCustomAttribute<ParameterAttribute, TProperty>(selector); return GetPropertyCustomAttribute<ParameterAttribute, TProperty>(selector);
} }
/// <summary> Gets custom <typeparamref name="TAttribute"/> <see cref="Attribute"/> from a given property. </summary> /// <summary> Gets custom <typeparamref name="TAttribute" /> <see cref="Attribute" /> from a given property. </summary>
/// <typeparam name="TAttribute"> Type of the attribute. </typeparam> /// <typeparam name="TAttribute"> Type of the attribute. </typeparam>
/// <typeparam name="TProperty"> Type of the property. </typeparam> /// <typeparam name="TProperty"> Type of the property. </typeparam>
/// <param name="selector"> The property selector function that identifies the property. </param> /// <param name="selector"> The property selector function that identifies the property. </param>
/// <returns> The custom <see cref="Attribute"/> of the property. </returns> /// <returns> The custom <see cref="Attribute" /> of the property. </returns>
public static TAttribute GetPropertyCustomAttribute<TAttribute, TProperty>(Expression<Func<T, TProperty>> selector) public static TAttribute GetPropertyCustomAttribute<TAttribute, TProperty>(Expression<Func<T, TProperty>> selector)
where TAttribute : Attribute where TAttribute : Attribute
{ {

View file

@ -1,18 +1,12 @@
using CapyKit.Attributes; using System.Security.Cryptography;
using CapyKit.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Threading.Tasks; using CapyKit.Attributes;
namespace CapyKit.Helpers namespace CapyKit.Helpers;
/// <summary> A class that contains methods for managing secure data processing and cryptography. </summary>
public class SecurityHelper
{ {
/// <summary> A class that contains methods for managing secure data processing and cryptography. </summary>
public class SecurityHelper
{
#region Members #region Members
/// <summary> A string of all the lower case characters. </summary> /// <summary> A string of all the lower case characters. </summary>
@ -39,9 +33,9 @@ namespace CapyKit.Helpers
} }
/// <summary> /// <summary>
/// Compares an unencrypted <paramref name="password"/> with a stored, encrypted <paramref name="existingPassword"/>. /// Compares an unencrypted <paramref name="password" /> with a stored, encrypted <paramref name="existingPassword" />.
/// This method uses the specified password algorithm type <typeparamref name="T"/> to retrieve the hashed version /// This method uses the specified password algorithm type <typeparamref name="T" /> to retrieve the hashed version
/// of the <paramref name="password"/> and then compares it with the <paramref name="existingPassword"/>. /// of the <paramref name="password" /> and then compares it with the <paramref name="existingPassword" />.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the password hashing algorithm.</typeparam> /// <typeparam name="T">The type of the password hashing algorithm.</typeparam>
/// <param name="existingPassword">The existing, encrypted password.</param> /// <param name="existingPassword">The existing, encrypted password.</param>
@ -49,7 +43,7 @@ namespace CapyKit.Helpers
/// <param name="salt">The salt value used in password hashing.</param> /// <param name="salt">The salt value used in password hashing.</param>
/// <param name="args">Additional arguments required for constructing the password algorithm instance.</param> /// <param name="args">Additional arguments required for constructing the password algorithm instance.</param>
/// <returns> /// <returns>
/// <see langword="true"/> if hash comparison succeeds, <see langword="false"/> if it fails. /// <see langword="true" /> if hash comparison succeeds, <see langword="false" /> if it fails.
/// </returns> /// </returns>
public static bool CompareHashedPassword<T>(Password existingPassword, string password, byte[] salt, public static bool CompareHashedPassword<T>(Password existingPassword, string password, byte[] salt,
params object[] args) params object[] args)
@ -66,7 +60,7 @@ namespace CapyKit.Helpers
/// <param name="password">The plaintext password to verify.</param> /// <param name="password">The plaintext password to verify.</param>
/// <param name="salt">The Base64-encoded persisted salt.</param> /// <param name="salt">The Base64-encoded persisted salt.</param>
/// <param name="args">Arguments used to construct the password algorithm.</param> /// <param name="args">Arguments used to construct the password algorithm.</param>
/// <returns><see langword="true"/> when the password matches the persisted hash.</returns> /// <returns><see langword="true" /> when the password matches the persisted hash.</returns>
public static bool CompareHashedPassword<T>(string storedHash, string password, string salt, public static bool CompareHashedPassword<T>(string storedHash, string password, string salt,
params object[] args) params object[] args)
where T : IPasswordAlgorithm where T : IPasswordAlgorithm
@ -89,16 +83,17 @@ namespace CapyKit.Helpers
} }
/// <summary> /// <summary>
/// Compares a hashed password with a plaintext password by using a specified hashing algorithm <typeparamref name="T"/>. /// Compares a hashed password with a plaintext password by using a specified hashing algorithm
/// <typeparamref name="T" />.
/// This method requires the existing hash, the plaintext password to compare, and the associated salt value. /// This method requires the existing hash, the plaintext password to compare, and the associated salt value.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the password hashing algorithm implementing <see cref="IPasswordAlgorithm"/>.</typeparam> /// <typeparam name="T">The type of the password hashing algorithm implementing <see cref="IPasswordAlgorithm" />.</typeparam>
/// <param name="storedHash">The stored hash of the password as a byte array.</param> /// <param name="storedHash">The stored hash of the password as a byte array.</param>
/// <param name="password">The plaintext password to verify.</param> /// <param name="password">The plaintext password to verify.</param>
/// <param name="salt">The salt used during the password hashing process as a byte array.</param> /// <param name="salt">The salt used during the password hashing process as a byte array.</param>
/// <param name="args">Optional arguments required for constructing the password hashing algorithm instance.</param> /// <param name="args">Optional arguments required for constructing the password hashing algorithm instance.</param>
/// <returns> /// <returns>
/// <see langword="true"/> if the hashed password matches the stored hash, <see langword="false"/> otherwise. /// <see langword="true" /> if the hashed password matches the stored hash, <see langword="false" /> otherwise.
/// </returns> /// </returns>
public static bool CompareHashedPassword<T>(byte[] storedHash, string password, byte[] salt, public static bool CompareHashedPassword<T>(byte[] storedHash, string password, byte[] salt,
params object[] args) params object[] args)
@ -124,17 +119,18 @@ namespace CapyKit.Helpers
} }
/// <summary> /// <summary>
/// Compares an unencrypted <paramref name="password"/> with a stored, encrypted <paramref name="existingPassword"/>. /// Compares an unencrypted <paramref name="password" /> with a stored, encrypted <paramref name="existingPassword" />.
/// This method uses the specified password hashing algorithm of type <typeparamref name="T"/> to hash the <paramref name="password"/> /// This method uses the specified password hashing algorithm of type <typeparamref name="T" /> to hash the
/// and then compares it with the <paramref name="existingPassword"/>. /// <paramref name="password" />
/// and then compares it with the <paramref name="existingPassword" />.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the password hashing algorithm, implementing <see cref="IPasswordAlgorithm"/>.</typeparam> /// <typeparam name="T">The type of the password hashing algorithm, implementing <see cref="IPasswordAlgorithm" />.</typeparam>
/// <param name="existingPassword">The stored, encrypted password to compare against.</param> /// <param name="existingPassword">The stored, encrypted password to compare against.</param>
/// <param name="password">The plain text password to be compared.</param> /// <param name="password">The plain text password to be compared.</param>
/// <param name="salt">The salt value used for hashing the plain text password.</param> /// <param name="salt">The salt value used for hashing the plain text password.</param>
/// <param name="args">Optional additional arguments required for initializing the password hashing algorithm.</param> /// <param name="args">Optional additional arguments required for initializing the password hashing algorithm.</param>
/// <returns> /// <returns>
/// <see langword="true"/> if the password hashes match, otherwise <see langword="false"/>. /// <see langword="true" /> if the password hashes match, otherwise <see langword="false" />.
/// </returns> /// </returns>
public static bool CompareHashedPassword(Password existingPassword, string password, byte[] salt, public static bool CompareHashedPassword(Password existingPassword, string password, byte[] salt,
IPasswordAlgorithm algorithm, params object[] args) IPasswordAlgorithm algorithm, params object[] args)
@ -142,7 +138,7 @@ namespace CapyKit.Helpers
var algorithmType = algorithm.GetType(); var algorithmType = algorithm.GetType();
var providedPassword = typeof(SecurityHelper) var providedPassword = typeof(SecurityHelper)
.GetMethod("GetPassword", new Type[] { typeof(string), typeof(byte[]), typeof(object[]) }) .GetMethod("GetPassword", new[] { typeof(string), typeof(byte[]), typeof(object[]) })
?.MakeGenericMethod(algorithmType) ?.MakeGenericMethod(algorithmType)
?.Invoke(null, new object[] { password, salt, args }); ?.Invoke(null, new object[] { password, salt, args });
@ -162,21 +158,22 @@ namespace CapyKit.Helpers
} }
/// <summary> /// <summary>
/// Retrieves a <see cref="Password"/> object using the specified password and generates a random salt value. /// Retrieves a <see cref="Password" /> object using the specified password and generates a random salt value.
/// Then it uses that salt to call the overloaded <see cref="GetPassword{T}(string, byte[], object[])"/> method with the given password and /// Then it uses that salt to call the overloaded <see cref="GetPassword{T}(string, byte[], object[])" /> method with
/// the given password and
/// the generated salt as arguments. /// the generated salt as arguments.
/// </summary> /// </summary>
/// <typeparam name="T"> The type of <see cref="IPasswordAlgorithm"/> implementation to use. </typeparam> /// <typeparam name="T"> The type of <see cref="IPasswordAlgorithm" /> implementation to use. </typeparam>
/// <param name="password"> The plaintext password to be hashed. </param> /// <param name="password"> The plaintext password to be hashed. </param>
/// <param name="args"> /// <param name="args">
/// Optional constructor arguments for the <see cref="IPasswordAlgorithm"/> implementation /// Optional constructor arguments for the <see cref="IPasswordAlgorithm" /> implementation
/// instance. /// instance.
/// </param> /// </param>
/// <returns> /// <returns>
/// A new <see cref="Password"/> object with the given password and a randomly generated salt, as well as an /// A new <see cref="Password" /> object with the given password and a randomly generated salt, as well as an
/// instance of <typeparamref name="T"/> created using any optional constructor arguments provided. /// instance of <typeparamref name="T" /> created using any optional constructor arguments provided.
/// </returns> /// </returns>
/// <seealso cref="SecurityHelper.SALT_SIZE"/> /// <seealso cref="SecurityHelper.SALT_SIZE" />
public static Password GetPassword<T>(string password, int saltSize, params object[] args) public static Password GetPassword<T>(string password, int saltSize, params object[] args)
where T : IPasswordAlgorithm where T : IPasswordAlgorithm
{ {
@ -185,15 +182,16 @@ namespace CapyKit.Helpers
} }
/// <summary> /// <summary>
/// Retrieves a <see cref="Password"/> object using the specified password, salt, and optional /// Retrieves a <see cref="Password" /> object using the specified password, salt, and optional
/// constructor arguments. /// constructor arguments.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This method uses reflection to find a constructor for the specified password algorithm type (<typeparamref name="T"/>). /// This method uses reflection to find a constructor for the specified password algorithm type (
/// <typeparamref name="T" />).
/// It emits an error event if a suitable constructor is not found or if there is an error invoking the constructor. /// It emits an error event if a suitable constructor is not found or if there is an error invoking the constructor.
/// </remarks> /// </remarks>
/// <typeparam name="T"> /// <typeparam name="T">
/// The type of <see cref="IPasswordAlgorithm"/> implementation to use. /// The type of <see cref="IPasswordAlgorithm" /> implementation to use.
/// </typeparam> /// </typeparam>
/// <param name="password"> The plaintext password to be hashed. </param> /// <param name="password"> The plaintext password to be hashed. </param>
/// <param name="salt"> /// <param name="salt">
@ -202,12 +200,12 @@ namespace CapyKit.Helpers
/// thus adding security. /// thus adding security.
/// </param> /// </param>
/// <param name="args"> /// <param name="args">
/// Optional constructor arguments for the <see cref="IPasswordAlgorithm"/> implementation /// Optional constructor arguments for the <see cref="IPasswordAlgorithm" /> implementation
/// instance. /// instance.
/// </param> /// </param>
/// <returns> /// <returns>
/// A new <see cref="Password"/> object with the given password and salt, as well as an instance /// A new <see cref="Password" /> object with the given password and salt, as well as an instance
/// of <typeparamref name="T"/> created using the provided constructor arguments. /// of <typeparamref name="T" /> created using the provided constructor arguments.
/// </returns> /// </returns>
public static Password GetPassword<T>(string password, byte[] salt, params object[] args) public static Password GetPassword<T>(string password, byte[] salt, params object[] args)
where T : IPasswordAlgorithm where T : IPasswordAlgorithm
@ -224,7 +222,7 @@ namespace CapyKit.Helpers
typeof(T).Name, typeof(T).Name,
string.Join(",", argTypes.Select(arg => arg.Name)) string.Join(",", argTypes.Select(arg => arg.Name))
}); });
return default(Password); return default;
} }
var passwordInstance = (T)algorithmConstructor.Invoke(args); var passwordInstance = (T)algorithmConstructor.Invoke(args);
@ -238,15 +236,16 @@ namespace CapyKit.Helpers
typeof(T).Name, typeof(T).Name,
string.Join(",", args) string.Join(",", args)
}); });
return default(Password); return default;
} }
return new Password(password, salt, passwordInstance, args); return new Password(password, salt, passwordInstance, args);
} }
/// <summary> /// <summary>
/// Generates a new <see cref="Password"/> object using the PBKDF2 algorithm with the provided <paramref name="password"/> /// Generates a new <see cref="Password" /> object using the PBKDF2 algorithm with the provided
/// and <paramref name="salt"/>. /// <paramref name="password" />
/// and <paramref name="salt" />.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This method uses the PBKDF2 (Password-Based Key Derivation Function 2) algorithm to generate /// This method uses the PBKDF2 (Password-Based Key Derivation Function 2) algorithm to generate
@ -258,7 +257,7 @@ namespace CapyKit.Helpers
/// A random value used to add an additional layer of security to the generated hash. /// A random value used to add an additional layer of security to the generated hash.
/// </param> /// </param>
/// <returns> /// <returns>
/// A new <see cref="Password"/> object containing the hashed password and salt. /// A new <see cref="Password" /> object containing the hashed password and salt.
/// </returns> /// </returns>
public static Password Pbkdf2(string password, byte[] salt) public static Password Pbkdf2(string password, byte[] salt)
{ {
@ -268,22 +267,23 @@ namespace CapyKit.Helpers
} }
/// <summary> /// <summary>
/// Generates a new <see cref="Password"/> object using the PBKDF2 algorithm with the provided <paramref name="password"/>. /// Generates a new <see cref="Password" /> object using the PBKDF2 algorithm with the provided
/// <paramref name="password" />.
/// This overload of the method generates a random salt value for added security. /// This overload of the method generates a random salt value for added security.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This method uses the PBKDF2 (Password-Based Key Derivation Function 2) algorithm to generate /// This method uses the PBKDF2 (Password-Based Key Derivation Function 2) algorithm to generate
/// a new password hash. The algorithm iteratively applies a pseudorandom function to the /// a new password hash. The algorithm iteratively applies a pseudorandom function to the
/// password and salt, which increases the security of the resulting hash. In this overload, /// password and salt, which increases the security of the resulting hash. In this overload,
/// a random salt value is generated using <see cref="SecurityHelper.GetRandomBytes(int)"/> method. /// a random salt value is generated using <see cref="SecurityHelper.GetRandomBytes(int)" /> method.
/// </remarks> /// </remarks>
/// <param name="password"> The clear text password to be hashed. </param> /// <param name="password"> The clear text password to be hashed. </param>
/// <returns> /// <returns>
/// A new <see cref="Password"/> object containing the hashed password and a randomly generated salt. /// A new <see cref="Password" /> object containing the hashed password and a randomly generated salt.
/// </returns> /// </returns>
public static Password Pbkdf2(string password, int saltSize) public static Password Pbkdf2(string password, int saltSize)
{ {
var salt = SecurityHelper.GetRandomBytes(saltSize); var salt = GetRandomBytes(saltSize);
var pwd = new Password(password, salt, Password.Pbkdf2Algorithm); var pwd = new Password(password, salt, Password.Pbkdf2Algorithm);
return pwd; return pwd;
@ -292,7 +292,7 @@ namespace CapyKit.Helpers
/// <summary> Gets a cryptographically strong random password. </summary> /// <summary> Gets a cryptographically strong random password. </summary>
/// <param name="length"> The length of the password to generate. </param> /// <param name="length"> The length of the password to generate. </param>
/// <param name="validCharacters"> /// <param name="validCharacters">
/// An array of <see cref="ValidCharacterCollection"/> enumeration values representing the desired /// An array of <see cref="ValidCharacterCollection" /> enumeration values representing the desired
/// character sets. /// character sets.
/// </param> /// </param>
/// <returns> The password. </returns> /// <returns> The password. </returns>
@ -318,7 +318,7 @@ namespace CapyKit.Helpers
/// <param name="first"> The first session identifier. </param> /// <param name="first"> The first session identifier. </param>
/// <param name="second"> The second session identifier. </param> /// <param name="second"> The second session identifier. </param>
/// <returns> /// <returns>
/// <see langword="true"/> if comparison succeeds, <see langword="false"/> if not. /// <see langword="true" /> if comparison succeeds, <see langword="false" /> if not.
/// </returns> /// </returns>
public static bool CompareSessionID(string first, string second) public static bool CompareSessionID(string first, string second)
{ {
@ -334,8 +334,8 @@ namespace CapyKit.Helpers
/// A convenience method to generate a random string of the specified length using all character sets. /// A convenience method to generate a random string of the specified length using all character sets.
/// </summary> /// </summary>
/// <param name="length"> The desired length of the generated random string.</param> /// <param name="length"> The desired length of the generated random string.</param>
/// <seealso cref="ValidCharacterCollection"/> /// <seealso cref="ValidCharacterCollection" />
/// <seealso cref="GetRandomString(int, ValidCharacterCollection[])"/> /// <seealso cref="GetRandomString(int, ValidCharacterCollection[])" />
public static string GetRandomString(int length) public static string GetRandomString(int length)
{ {
return GetRandomString(length, return GetRandomString(length,
@ -345,7 +345,10 @@ namespace CapyKit.Helpers
ValidCharacterCollection.Special); ValidCharacterCollection.Special);
} }
/// <summary> Gets a cryptographically strong random string using the character values found in <see cref="VALID_CHARACTERS"/>. </summary> /// <summary>
/// Gets a cryptographically strong random string using the character values found in
/// <see cref="VALID_CHARACTERS" />.
/// </summary>
/// <param name="length"> The length of the string to create. </param> /// <param name="length"> The length of the string to create. </param>
/// <returns> The random string. </returns> /// <returns> The random string. </returns>
public static string GetRandomString(int length, params ValidCharacterCollection[] validChars) public static string GetRandomString(int length, params ValidCharacterCollection[] validChars)
@ -355,7 +358,7 @@ namespace CapyKit.Helpers
new byte[length * 3]; // Overprovision the buffer so we can discard a small percentage. new byte[length * 3]; // Overprovision the buffer so we can discard a small percentage.
var validCharacters = GetValidCharacterComposition(validChars); var validCharacters = GetValidCharacterComposition(validChars);
var validByteUpperLimit = var validByteUpperLimit =
(256 / validCharacters.Length) * validCharacters.Length - 256 / validCharacters.Length * validCharacters.Length -
1; // Maintains equal distribution of valid characters. 1; // Maintains equal distribution of valid characters.
using (var rng = RandomNumberGenerator.Create()) using (var rng = RandomNumberGenerator.Create())
@ -363,11 +366,11 @@ namespace CapyKit.Helpers
while (buffer.Length < length) while (buffer.Length < length)
{ {
rng.GetBytes(randomNumberBuffer); rng.GetBytes(randomNumberBuffer);
foreach (byte b in randomNumberBuffer) foreach (var b in randomNumberBuffer)
{ {
if (b <= validByteUpperLimit) if (b <= validByteUpperLimit)
{ {
int index = b % validCharacters.Length; var index = b % validCharacters.Length;
buffer.Append(validCharacters[index]); buffer.Append(validCharacters[index]);
if (buffer.Length == length) if (buffer.Length == length)
{ {
@ -405,7 +408,10 @@ namespace CapyKit.Helpers
/// <summary> /// <summary>
/// Static method that returns a valid character composition based on the given ValidCharacterCollection parameters. /// Static method that returns a valid character composition based on the given ValidCharacterCollection parameters.
/// </summary> /// </summary>
/// <param name="validCharacters">An array of ValidCharacterCollection enumeration values representing the desired character sets.</param> /// <param name="validCharacters">
/// An array of ValidCharacterCollection enumeration values representing the desired
/// character sets.
/// </param>
/// <returns>A string containing all the characters from the specified character sets.</returns> /// <returns>A string containing all the characters from the specified character sets.</returns>
public static string GetValidCharacterComposition(params ValidCharacterCollection[] validCharacters) public static string GetValidCharacterComposition(params ValidCharacterCollection[] validCharacters)
{ {
@ -426,8 +432,6 @@ namespace CapyKit.Helpers
case ValidCharacterCollection.Special: case ValidCharacterCollection.Special:
composition.Append(SecurityHelper.SPECIAL_CHARACTERS); composition.Append(SecurityHelper.SPECIAL_CHARACTERS);
break; break;
default:
break;
} }
} }
@ -438,12 +442,12 @@ namespace CapyKit.Helpers
/// <param name="first"> The first string. </param> /// <param name="first"> The first string. </param>
/// <param name="second"> The second string. </param> /// <param name="second"> The second string. </param>
/// <returns> /// <returns>
/// <see langword="true"/> if the comparison succeeds, <see langword="false"/> if not. /// <see langword="true" /> if the comparison succeeds, <see langword="false" /> if not.
/// </returns> /// </returns>
/// <remarks> /// <remarks>
/// This method is a proxy for using /// This method is a proxy for using
/// <see cref="string.Compare(string, int, string, int, int, StringComparison)"/> with the /// <see cref="string.Compare(string, int, string, int, int, StringComparison)" /> with the
/// <c>StringComparison</c> set to <see cref="StringComparison.Ordinal"/>. /// <c>StringComparison</c> set to <see cref="StringComparison.Ordinal" />.
/// </remarks> /// </remarks>
private static bool CompareStrings(string first, string second) private static bool CompareStrings(string first, string second)
{ {
@ -451,13 +455,13 @@ namespace CapyKit.Helpers
} }
#endregion Methods #endregion Methods
} }
/// <summary> /// <summary>
/// An enumeration that defines the types of characters that can be included in a random string. /// An enumeration that defines the types of characters that can be included in a random string.
/// </summary> /// </summary>
public enum ValidCharacterCollection public enum ValidCharacterCollection
{ {
/// <summary> /// <summary>
/// Indicates that lower case characters should be included in the random string. /// Indicates that lower case characters should be included in the random string.
/// </summary> /// </summary>
@ -480,6 +484,5 @@ namespace CapyKit.Helpers
/// Indicates that special characters should be included in the random string. /// Indicates that special characters should be included in the random string.
/// </summary> /// </summary>
[EnumerationDescriptionAttribute(SecurityHelper.SPECIAL_CHARACTERS)] [EnumerationDescriptionAttribute(SecurityHelper.SPECIAL_CHARACTERS)]
Special, Special
}
} }

View file

@ -1,17 +1,9 @@
using System; using System.Text.Json;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace CapyKit.Helpers namespace CapyKit.Helpers;
public static class SerializationHelper
{ {
public static class SerializationHelper
{
#region Members #region Members
// //
@ -36,11 +28,11 @@ namespace CapyKit.Helpers
return JsonSerializer.Serialize(obj); return JsonSerializer.Serialize(obj);
} }
/// <summary> Deserializes an object to a given <typeparamref name="T"/> type. </summary> /// <summary> Deserializes an object to a given <typeparamref name="T" /> type. </summary>
/// <exception cref="FormatException"> Thrown when the format of the byte array is incorrect. </exception> /// <exception cref="FormatException"> Thrown when the format of the byte array is incorrect. </exception>
/// <typeparam name="T"> Generic type parameter. </typeparam> /// <typeparam name="T"> Generic type parameter. </typeparam>
/// <param name="bytes"> The byte array representing a <typeparamref name="T"/> object. </param> /// <param name="bytes"> The byte array representing a <typeparamref name="T" /> object. </param>
/// <returns> A <typeparamref name="T"/> object. </returns> /// <returns> A <typeparamref name="T" /> object. </returns>
public static T Deserialize<T>(byte[] bytes) public static T Deserialize<T>(byte[] bytes)
{ {
var stream = new MemoryStream(bytes); var stream = new MemoryStream(bytes);
@ -48,20 +40,20 @@ namespace CapyKit.Helpers
return Deserialize<T>(stream); return Deserialize<T>(stream);
} }
/// <summary> Deserializes an object to a given <typeparamref name="T"/> type. </summary> /// <summary> Deserializes an object to a given <typeparamref name="T" /> type. </summary>
/// <exception cref="FormatException"> /// <exception cref="FormatException">
/// Thrown when the format of an input is incorrect. /// Thrown when the format of an input is incorrect.
/// </exception> /// </exception>
/// <typeparam name="T"> Generic type parameter. </typeparam> /// <typeparam name="T"> Generic type parameter. </typeparam>
/// <param name="stream"> The steam. </param> /// <param name="stream"> The steam. </param>
/// <returns> A <typeparamref name="T"/> object. </returns> /// <returns> A <typeparamref name="T" /> object. </returns>
public static T Deserialize<T>(Stream stream) public static T Deserialize<T>(Stream stream)
{ {
try try
{ {
var obj = JsonSerializer.Deserialize<T>(stream); var obj = JsonSerializer.Deserialize<T>(stream);
if(obj == null) if (obj == null)
{ {
CapyEventReporter.EmitEvent(EventLevel.Error, "The deserialized object was null."); CapyEventReporter.EmitEvent(EventLevel.Error, "The deserialized object was null.");
throw new ArgumentNullException(nameof(stream)); throw new ArgumentNullException(nameof(stream));
@ -71,35 +63,40 @@ namespace CapyKit.Helpers
} }
catch (JsonException ex) catch (JsonException ex)
{ {
CapyEventReporter.EmitEvent(EventLevel.Error, "JSON formatting error detected during deserialization of byte array for {0}.", args: new[] { typeof(T).Name }); CapyEventReporter.EmitEvent(EventLevel.Error,
throw new FormatException(string.Format("JSON formatting error detected during deserialization of byte array for {0}.", typeof(T).Name), ex); "JSON formatting error detected during deserialization of byte array for {0}.",
args: new[] { typeof(T).Name });
throw new FormatException(
string.Format("JSON formatting error detected during deserialization of byte array for {0}.",
typeof(T).Name), ex);
} }
catch (Exception ex) catch (Exception ex)
{ {
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not deserialize object of type {0}.", args: new[] { typeof(T).Name }); CapyEventReporter.EmitEvent(EventLevel.Error, "Could not deserialize object of type {0}.",
args: new[] { typeof(T).Name });
throw; throw;
} }
} }
/// <summary> Deserializes a <c>JSON</c> encoded string to the given <typeparamref name="T"/>. </summary> /// <summary> Deserializes a <c>JSON</c> encoded string to the given <typeparamref name="T" />. </summary>
/// <typeparam name="T"> Generic type parameter. </typeparam> /// <typeparam name="T"> Generic type parameter. </typeparam>
/// <param name="str"> The <c>JSON</c> encoded string representing a <typeparamref name="T"/> object. </param> /// <param name="str"> The <c>JSON</c> encoded string representing a <typeparamref name="T" /> object. </param>
/// <returns> A <typeparamref name="T"/> object. </returns> /// <returns> A <typeparamref name="T" /> object. </returns>
public static T Deserialize<T>(string str) public static T Deserialize<T>(string str)
{ {
if (typeof(T) == typeof(string)) if (typeof(T) == typeof(string))
{ {
return (T)Convert.ChangeType(str, typeof(T)); return (T)Convert.ChangeType(str, typeof(T));
} }
else if(string.IsNullOrWhiteSpace(str))
if (string.IsNullOrWhiteSpace(str))
{ {
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not deserialize an empty string."); CapyEventReporter.EmitEvent(EventLevel.Error, "Could not deserialize an empty string.");
return default(T); return default;
} }
return JsonSerializer.Deserialize<T>(str); return JsonSerializer.Deserialize<T>(str);
} }
#endregion Methods #endregion Methods
}
} }

View file

@ -1,66 +1,60 @@
using System; namespace CapyKit.Helpers;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CapyKit.Helpers /// <summary>
/// Static class containing helper methods for retrieving and setting application settings.
/// </summary>
/// <remarks>
/// The specific means of accessing and storing the settings are determined by the consumer,
/// allowing for flexibility in various environments such as <c>App.config</c> or <c>Web.config</c>
/// .
/// </remarks>
/// <example>
/// This example demonstrates how to set up the SettingsHelper class with custom accessor and
/// detector methods that read from an App.config file. The setup is done at the beginning of the
/// application execution, before any other usage of the helper methods.
/// <code>
/// public int main(string[] args)
/// {
/// // Set up SettingsHelper with custom accessor and detector methods
/// Func&lt;string, object&gt; accessor = (key) =&gt;
/// {
/// Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
/// return config.AppSettings.Settings[key].Value;
/// };
///
/// Func&lt;string, bool&gt; detector = (key) =&gt;
/// {
/// Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
/// return config.AppSettings.Settings.AllKeys.Contains(key);
/// };
///
/// SettingsHelper.SetAccessorMethod(accessor);
/// SettingsHelper.SetDetectorMethod(detector);
///
/// // Use the helper to retrieve and set settings
/// SettingsHelper.SetApplicationSetting&lt;int&gt;("MySettingKey", 42);
/// int newSetting = SettingsHelper.GetApplicationSetting&lt;int&gt;("MySettingKey");
/// Console.WriteLine("New setting: {0}", newSetting);
///
/// int mySetting = SettingsHelper.GetApplicationSetting&lt;int&gt;("MySettingKey");
/// Console.WriteLine("Retrieved setting: {0}", mySetting);
/// }
/// </code>
/// </example>
public static class SettingsHelper
{ {
/// <summary>
/// Static class containing helper methods for retrieving and setting application settings.
/// </summary>
/// <remarks>
/// The specific means of accessing and storing the settings are determined by the consumer,
/// allowing for flexibility in various environments such as <c>App.config</c> or <c>Web.config</c>
/// .
/// </remarks>
/// <example>
/// This example demonstrates how to set up the SettingsHelper class with custom accessor and
/// detector methods that read from an App.config file. The setup is done at the beginning of the
/// application execution, before any other usage of the helper methods.
/// <code>
/// public int main(string[] args)
/// {
/// // Set up SettingsHelper with custom accessor and detector methods
/// Func&lt;string, object&gt; accessor = (key) =&gt;
/// {
/// Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
/// return config.AppSettings.Settings[key].Value;
/// };
///
/// Func&lt;string, bool&gt; detector = (key) =&gt;
/// {
/// Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
/// return config.AppSettings.Settings.AllKeys.Contains(key);
/// };
///
/// SettingsHelper.SetAccessorMethod(accessor);
/// SettingsHelper.SetDetectorMethod(detector);
///
/// // Use the helper to retrieve and set settings
/// SettingsHelper.SetApplicationSetting&lt;int&gt;("MySettingKey", 42);
/// int newSetting = SettingsHelper.GetApplicationSetting&lt;int&gt;("MySettingKey");
/// Console.WriteLine("New setting: {0}", newSetting);
///
/// int mySetting = SettingsHelper.GetApplicationSetting&lt;int&gt;("MySettingKey");
/// Console.WriteLine("Retrieved setting: {0}", mySetting);
/// }
/// </code>
/// </example>
public static class SettingsHelper
{
#region Members #region Members
/// <summary> /// <summary>
/// Private delegate function that retrieves a setting with the given <c>key</c>. /// Private delegate function that retrieves a setting with the given <c>key</c>.
/// </summary> /// </summary>
private static Func<string, object> accessor = (key) => default(object); private static Func<string, object> accessor = key => default;
/// <summary> /// <summary>
/// Private delegate function that detects if a setting with a given <c>key</c> exists. Returns <see langword="true"/> /// Private delegate function that detects if a setting with a given <c>key</c> exists. Returns <see langword="true" />
/// if the setting exists, <see langword="false"/> if not. /// if the setting exists, <see langword="false" /> if not.
/// </summary> /// </summary>
private static Func<string, bool> detector = (key) => false; private static Func<string, bool> detector = key => false;
#endregion Members #endregion Members
@ -71,7 +65,7 @@ namespace CapyKit.Helpers
/// </summary> /// </summary>
/// <typeparam name="T"> The type of the setting to be retrieved. </typeparam> /// <typeparam name="T"> The type of the setting to be retrieved. </typeparam>
/// <param name="settingName"> The name of the setting to retrieve. </param> /// <param name="settingName"> The name of the setting to retrieve. </param>
/// <returns> The value of the setting as an uncast <typeparamref name="T"/>. </returns> /// <returns> The value of the setting as an uncast <typeparamref name="T" />. </returns>
public static T GetApplicationSetting<T>(string settingName) public static T GetApplicationSetting<T>(string settingName)
{ {
if (SettingsHelper.detector(settingName)) if (SettingsHelper.detector(settingName))
@ -82,10 +76,10 @@ namespace CapyKit.Helpers
return (T)result; return (T)result;
} }
return default(T); return default;
} }
return default(T); return default;
} }
/// <summary> Sets the function used to retrieve application settings. </summary> /// <summary> Sets the function used to retrieve application settings. </summary>
@ -131,5 +125,4 @@ namespace CapyKit.Helpers
} }
#endregion Methods #endregion Methods
}
} }

File diff suppressed because it is too large Load diff

View file

@ -1,55 +1,15 @@
using System; using System.Security.Cryptography;
using System.Collections.Generic;
using System.Data.SqlTypes;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace CapyKit namespace CapyKit;
/// <summary>
/// Represents a password with its hash, salt and algorithm used for encryption.
/// </summary>
public class Password
{ {
/// <summary>
/// Represents a password with its hash, salt and algorithm used for encryption.
/// </summary>
public class Password
{
#region Members #region Members
private static Lazy<Pbkdf2Algorithm> pbkdf2Algorithm = new Lazy<Pbkdf2Algorithm>(() => new Pbkdf2Algorithm()); private static readonly Lazy<Pbkdf2Algorithm> pbkdf2Algorithm = new(() => new Pbkdf2Algorithm());
#endregion
#region Properties
/// <summary>
/// Gets or sets the hash of the password.
/// </summary>
public byte[] Hash { get; private set; }
/// <summary>
/// Gets or sets the salt used for encryption.
/// </summary>
public byte[] Salt { get; private set; }
/// <summary>
/// Gets or sets the algorithm used for password encryption.
/// </summary>
public IPasswordAlgorithm Algorithm { get; private set; }
#region Preconfigued Password Algorithms
/// <summary> Gets the preconfigured PBKDF2 algorithm. </summary>
/// <value> The preconfigured PBKDF2 algorithm. </value>
public static Pbkdf2Algorithm Pbkdf2Algorithm
{
get
{
return pbkdf2Algorithm.Value;
}
}
#endregion
#endregion #endregion
@ -57,7 +17,10 @@ namespace CapyKit
/// <param name="password"> The password to be hashed. </param> /// <param name="password"> The password to be hashed. </param>
/// <param name="salt"> The salt used for encryption. </param> /// <param name="salt"> The salt used for encryption. </param>
/// <param name="algorithm"> The algorithm used for password encryption. </param> /// <param name="algorithm"> The algorithm used for password encryption. </param>
/// <param name="args"> A variable-length parameters list containing arguments to include for the <paramref name="algorithm"/>. </param> /// <param name="args">
/// A variable-length parameters list containing arguments to include for the
/// <paramref name="algorithm" />.
/// </param>
public Password(string password, byte[] salt, IPasswordAlgorithm algorithm, params object[] args) public Password(string password, byte[] salt, IPasswordAlgorithm algorithm, params object[] args)
{ {
// We know there will always be a salt, so we can prepend it to h // We know there will always be a salt, so we can prepend it to h
@ -79,14 +42,50 @@ namespace CapyKit
this.Algorithm = algorithm; this.Algorithm = algorithm;
} }
#region Properties
/// <summary>
/// Gets or sets the hash of the password.
/// </summary>
public byte[] Hash { get; }
/// <summary>
/// Gets or sets the salt used for encryption.
/// </summary>
public byte[] Salt { get; }
/// <summary>
/// Gets or sets the algorithm used for password encryption.
/// </summary>
public IPasswordAlgorithm Algorithm { get; }
#region Preconfigued Password Algorithms
/// <summary> Gets the preconfigured PBKDF2 algorithm. </summary>
/// <value> The preconfigured PBKDF2 algorithm. </value>
public static Pbkdf2Algorithm Pbkdf2Algorithm
{
get { return Password.pbkdf2Algorithm.Value; }
}
#endregion
#endregion
#region Methods #region Methods
/// <inheritdoc/> /// <inheritdoc />
public override bool Equals(object? obj) public override bool Equals(object? obj)
{ {
if(obj == null) return false; // The object is null, and this object is not. if (obj == null)
{
return false; // The object is null, and this object is not.
}
if(ReferenceEquals(this, obj)) return true; // The object is literally this object. if (ReferenceEquals(this, obj))
{
return true; // The object is literally this object.
}
var objPassword = obj as Password; var objPassword = obj as Password;
@ -100,38 +99,39 @@ namespace CapyKit
&& this.Salt.SequenceEqual(objPassword.Salt); && this.Salt.SequenceEqual(objPassword.Salt);
} }
/// <inheritdoc/> /// <inheritdoc />
public override string ToString() public override string ToString()
{ {
return string.Format("Hash: {0}, Salt: {1}, Algorithm: {2}", BitConverter.ToString(this.Hash), BitConverter.ToString(this.Salt), this.Algorithm?.AlgorithmName); return string.Format("Hash: {0}, Salt: {1}, Algorithm: {2}", BitConverter.ToString(this.Hash),
BitConverter.ToString(this.Salt), this.Algorithm?.AlgorithmName);
} }
#endregion #endregion
#region Operators #region Operators
/// <inheritdoc/> /// <inheritdoc />
public static bool operator ==(Password a, Password b) public static bool operator ==(Password a, Password b)
{ {
return ReferenceEquals(a, b) // Literally the same object. return ReferenceEquals(a, b) // Literally the same object.
|| (ReferenceEquals(a, null) && ReferenceEquals(b,null)) // Both are null || (ReferenceEquals(a, null) && ReferenceEquals(b, null)) // Both are null
|| a.Equals(b); // Both are not null but not the same object. || a.Equals(b); // Both are not null but not the same object.
} }
/// <inheritdoc/> /// <inheritdoc />
public static bool operator !=(Password a, Password b) public static bool operator !=(Password a, Password b)
{ {
return !(a == b); return !(a == b);
} }
#endregion #endregion
} }
/// <summary> /// <summary>
/// Defines the contract for password encryption algorithms. /// Defines the contract for password encryption algorithms.
/// </summary> /// </summary>
public interface IPasswordAlgorithm public interface IPasswordAlgorithm
{ {
#region Properties #region Properties
/// <summary> /// <summary>
@ -148,7 +148,7 @@ namespace CapyKit
/// <param name="args"> /// <param name="args">
/// Additional arguments for the encryption process, such as salt and length. /// Additional arguments for the encryption process, such as salt and length.
/// </param> /// </param>
/// <returns> A byte array with the hashed <paramref name="password"/>. </returns> /// <returns> A byte array with the hashed <paramref name="password" />. </returns>
byte[] Encrypt(string password, params object[] args); byte[] Encrypt(string password, params object[] args);
/// <summary> /// <summary>
@ -160,7 +160,7 @@ namespace CapyKit
/// Additional arguments for the encryption process, such as salt and length. /// Additional arguments for the encryption process, such as salt and length.
/// </param> /// </param>
/// <returns> /// <returns>
/// True if the given <paramref name="password"/> matches the <paramref name="encryptedValue"/>, /// True if the given <paramref name="password" /> matches the <paramref name="encryptedValue" />,
/// false if they are different. /// false if they are different.
/// </returns> /// </returns>
bool Compare(string password, byte[] encryptedValue, params object[] args) bool Compare(string password, byte[] encryptedValue, params object[] args)
@ -171,40 +171,15 @@ namespace CapyKit
} }
#endregion #endregion
} }
#region Implementations #region Implementations
/// <summary>
/// Implements the PBKDF2 algorithm for password encryption.
/// </summary>
public class Pbkdf2Algorithm : IPasswordAlgorithm
{
#region Members
/// <summary> (Immutable) The default length. </summary>
/// <remarks> This member is immutable. </remarks>
public const int LENGTH = 32;
/// <summary> The default number of iterations. </summary>
/// <remarks> This member is immutable. </remarks>
public const int ITERATIONS = 100000;
#endregion
#region Properties
/// <inheritdoc/>
public string AlgorithmName
{
get
{
return "Pbkdf2";
}
}
#endregion
/// <summary>
/// Implements the PBKDF2 algorithm for password encryption.
/// </summary>
public class Pbkdf2Algorithm : IPasswordAlgorithm
{
#region Constructor #region Constructor
/// <summary> Default constructor. </summary> /// <summary> Default constructor. </summary>
@ -215,6 +190,18 @@ namespace CapyKit
#endregion #endregion
#region IPasswordAlgorithm Members
#region Properties
/// <inheritdoc />
public string AlgorithmName
{
get { return "Pbkdf2"; }
}
#endregion
#region Methods #region Methods
/// <summary> Encrypts the given password using a PBKDF2 algorithm. </summary> /// <summary> Encrypts the given password using a PBKDF2 algorithm. </summary>
@ -222,12 +209,18 @@ namespace CapyKit
/// <param name="args"> /// <param name="args">
/// Additional arguments for the encryption process, specifically /// Additional arguments for the encryption process, specifically
/// <list type="number"> /// <list type="number">
/// <item><c>salt</c></item> /// <item>
/// <item><c>length</c></item> /// <c>salt</c>
/// <item><c>iterations</c></item> /// </item>
/// <item>
/// <c>length</c>
/// </item>
/// <item>
/// <c>iterations</c>
/// </item>
/// </list> /// </list>
/// </param> /// </param>
/// <returns> A byte array with the hashed <paramref name="password"/>. </returns> /// <returns> A byte array with the hashed <paramref name="password" />. </returns>
public byte[] Encrypt(string password, params object[] args) public byte[] Encrypt(string password, params object[] args)
{ {
if (args.Length == 0) if (args.Length == 0)
@ -252,7 +245,7 @@ namespace CapyKit
catch (Exception ex) catch (Exception ex)
{ {
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not convert the second parameter into an integer."); CapyEventReporter.EmitEvent(EventLevel.Error, "Could not convert the second parameter into an integer.");
length = LENGTH; length = Pbkdf2Algorithm.LENGTH;
} }
var iterations = 0; var iterations = 0;
@ -263,7 +256,7 @@ namespace CapyKit
catch (Exception ex) catch (Exception ex)
{ {
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not convert the second parameter into an integer."); CapyEventReporter.EmitEvent(EventLevel.Error, "Could not convert the second parameter into an integer.");
iterations = ITERATIONS; iterations = Pbkdf2Algorithm.ITERATIONS;
} }
var hash = new byte[0]; var hash = new byte[0];
@ -283,7 +276,20 @@ namespace CapyKit
} }
#endregion #endregion
}
#endregion
#region Members
/// <summary> (Immutable) The default length. </summary>
/// <remarks> This member is immutable. </remarks>
public const int LENGTH = 32;
/// <summary> The default number of iterations. </summary>
/// <remarks> This member is immutable. </remarks>
public const int ITERATIONS = 100000;
#endregion #endregion
} }
#endregion

View file

@ -1,20 +1,14 @@
using System; using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace CapyKit namespace CapyKit;
/// <summary>
/// A managed pool of resources. This class provides a thread-safe way to manage a collection of
/// objects of type <typeparamref name="T" />.
/// </summary>
/// <typeparam name="T"> The type of objects to be managed by the pool. </typeparam>
public class Pool<T>
{ {
/// <summary>
/// A managed pool of resources. This class provides a thread-safe way to manage a collection of
/// objects of type <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T"> The type of objects to be managed by the pool. </typeparam>
public class Pool<T>
{
#region Members #region Members
/// <summary> The collection of pooled items. </summary> /// <summary> The collection of pooled items. </summary>
@ -28,7 +22,7 @@ namespace CapyKit
#region Constructors #region Constructors
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Pool{T}"/> class with the specified pool size. /// Initializes a new instance of the <see cref="Pool{T}" /> class with the specified pool size.
/// </summary> /// </summary>
/// <param name="poolSize"> The size of the pool. </param> /// <param name="poolSize"> The size of the pool. </param>
public Pool(int poolSize) public Pool(int poolSize)
@ -39,12 +33,12 @@ namespace CapyKit
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Pool{T}"/> class with the specified pool size /// Initializes a new instance of the <see cref="Pool{T}" /> class with the specified pool size
/// and constructor selector. /// and constructor selector.
/// </summary> /// </summary>
/// <param name="poolSize"> The size of the pool. </param> /// <param name="poolSize"> The size of the pool. </param>
/// <param name="constructorSelector"> /// <param name="constructorSelector">
/// The constructor selector used to create new instances of <typeparamref name="T"/>. /// The constructor selector used to create new instances of <typeparamref name="T" />.
/// </param> /// </param>
public Pool(int poolSize, Func<T> constructorSelector) public Pool(int poolSize, Func<T> constructorSelector)
{ {
@ -54,11 +48,11 @@ namespace CapyKit
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Pool{T}"/> class with the specified collection /// Initializes a new instance of the <see cref="Pool{T}" /> class with the specified collection
/// of items. /// of items.
/// </summary> /// </summary>
/// <param name="collection"> /// <param name="collection">
/// The collection of <typeparamref name="T"/> items with which to seed the pool. /// The collection of <typeparamref name="T" /> items with which to seed the pool.
/// </param> /// </param>
public Pool(IEnumerable<T> collection) public Pool(IEnumerable<T> collection)
{ {
@ -77,7 +71,7 @@ namespace CapyKit
/// <param name="poolSize"> The size of the pool. </param> /// <param name="poolSize"> The size of the pool. </param>
private void FillPoolItemCollection(int poolSize) private void FillPoolItemCollection(int poolSize)
{ {
FillPoolItemCollection(poolSize, () => default(T)); FillPoolItemCollection(poolSize, () => default);
} }
/// <summary> /// <summary>
@ -88,17 +82,17 @@ namespace CapyKit
/// <param name="constructorSelector"> The constructor selector. </param> /// <param name="constructorSelector"> The constructor selector. </param>
private void FillPoolItemCollection(int poolSize, Func<T> constructorSelector) private void FillPoolItemCollection(int poolSize, Func<T> constructorSelector)
{ {
for (int i = 0; i < poolSize; i++) for (var i = 0; i < poolSize; i++)
{ {
this.poolItemCollection.Add(new PoolItem<T>(constructorSelector(), i)); this.poolItemCollection.Add(new PoolItem<T>(constructorSelector(), i));
} }
} }
/// <summary> Fill the pool item collection from an existing <typeparamref name="T"/> collection. </summary> /// <summary> Fill the pool item collection from an existing <typeparamref name="T" /> collection. </summary>
/// <param name="collection"> The <typeparamref name="T"/> collection. </param> /// <param name="collection"> The <typeparamref name="T" /> collection. </param>
private void FillPoolItemCollection(IEnumerable<T> collection) private void FillPoolItemCollection(IEnumerable<T> collection)
{ {
int index = 0; var index = 0;
foreach (var item in collection) foreach (var item in collection)
{ {
this.poolItemCollection.Add(new PoolItem<T>(item, index++)); this.poolItemCollection.Add(new PoolItem<T>(item, index++));
@ -116,7 +110,8 @@ namespace CapyKit
var firstAvailableItem = this.poolItemCollection.First(item => !item.Locked); var firstAvailableItem = this.poolItemCollection.First(item => !item.Locked);
firstAvailableItem.SetLock(); firstAvailableItem.SetLock();
CapyEventReporter.EmitEvent(EventLevel.Debug, "Accessed ppol and retrieved {0}", args: new[] { firstAvailableItem }); CapyEventReporter.EmitEvent(EventLevel.Debug, "Accessed ppol and retrieved {0}",
args: new[] { firstAvailableItem });
return firstAvailableItem; return firstAvailableItem;
} }
@ -128,8 +123,8 @@ namespace CapyKit
/// <summary> Releases the lock on the specified item and returns it to the pool. </summary> /// <summary> Releases the lock on the specified item and returns it to the pool. </summary>
/// <remarks> /// <remarks>
/// This method sets the <see cref="PoolItem{T}.Locked"/> flag to <see langword="false"/> so that /// This method sets the <see cref="PoolItem{T}.Locked" /> flag to <see langword="false" /> so that
/// it can be retrieved by <see cref="Pool{T}.GetAvailableItem"/>. /// it can be retrieved by <see cref="Pool{T}.GetAvailableItem" />.
/// </remarks> /// </remarks>
/// <param name="item"> The item to release. </param> /// <param name="item"> The item to release. </param>
public void ReleaseItem(PoolItem<T> item) public void ReleaseItem(PoolItem<T> item)
@ -138,76 +133,16 @@ namespace CapyKit
} }
#endregion Methods #endregion Methods
} }
/// <summary> A pool item. This class cannot be inherited. </summary>
/// <typeparam name="T"> The type of the pooled item. </typeparam>
public sealed class PoolItem<T>
{
#region Members
/// <summary> The pooled item. </summary>
private readonly T item;
/// <summary> A flag indicating whether the item is locked or not. </summary>
private bool locked;
/// <summary> The zero-based index of the pooled item. </summary>
private readonly int index;
/// <summary> The name of the pooled item <see cref="Type"/>. </summary>
private readonly string typeName;
#endregion Members
#region Properties
/// <summary> Gets the pooled resource. </summary>
/// <value> The pooled resource. </value>
public T Item
{
get
{
return this.item;
}
}
/// <summary> Gets a value indicating whether this object is locked or not. </summary>
/// <value> A value indicating whether this object is locked or not. </value>
public bool Locked
{
get
{
return this.locked;
}
}
/// <summary> Gets the zero-based index of the pooled item. </summary>
/// <value> The index. </value>
public int Index
{
get
{
return this.index;
}
}
/// <summary> Gets the name of the <see cref="Type"/> of the pooled item. </summary>
/// <value> The name of the <see cref="Type"/> of the pooled item. </value>
public string TypeName
{
get
{
return this.typeName;
}
}
#endregion Properties
/// <summary> A pool item. This class cannot be inherited. </summary>
/// <typeparam name="T"> The type of the pooled item. </typeparam>
public sealed class PoolItem<T>
{
#region Constructors #region Constructors
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PoolItem{T}"/> class with the specified item and /// Initializes a new instance of the <see cref="PoolItem{T}" /> class with the specified item and
/// index. /// index.
/// </summary> /// </summary>
/// <param name="item"> The pooled item. </param> /// <param name="item"> The pooled item. </param>
@ -222,19 +157,79 @@ namespace CapyKit
#endregion Constructors #endregion Constructors
#region Overrides
/// <summary> Returns a string that represents the current object and its lock state. </summary>
/// <returns> A string that represents the current object and its lock state. </returns>
public override string ToString()
{
return string.Format("{0} {1} ({2})", this.typeName, this.index, this.locked ? "Locked" : "Unlocked");
}
#endregion Overrides
#region Members
/// <summary> The pooled item. </summary>
private readonly T item;
/// <summary> A flag indicating whether the item is locked or not. </summary>
private bool locked;
/// <summary> The zero-based index of the pooled item. </summary>
private readonly int index;
/// <summary> The name of the pooled item <see cref="Type" />. </summary>
private readonly string typeName;
#endregion Members
#region Properties
/// <summary> Gets the pooled resource. </summary>
/// <value> The pooled resource. </value>
public T Item
{
get { return this.item; }
}
/// <summary> Gets a value indicating whether this object is locked or not. </summary>
/// <value> A value indicating whether this object is locked or not. </value>
public bool Locked
{
get { return this.locked; }
}
/// <summary> Gets the zero-based index of the pooled item. </summary>
/// <value> The index. </value>
public int Index
{
get { return this.index; }
}
/// <summary> Gets the name of the <see cref="Type" /> of the pooled item. </summary>
/// <value> The name of the <see cref="Type" /> of the pooled item. </value>
public string TypeName
{
get { return this.typeName; }
}
#endregion Properties
#region Methods #region Methods
/// <summary> Sets the lock on the item indicating that it is in use. </summary> /// <summary> Sets the lock on the item indicating that it is in use. </summary>
/// <remarks> If the item is already locked, an error event is emitted. </remarks> /// <remarks> If the item is already locked, an error event is emitted. </remarks>
/// <returns> /// <returns>
/// <see langword="true"/> if the item is locked successfully, <see langword="false"/> if it /// <see langword="true" /> if the item is locked successfully, <see langword="false" /> if it
/// fails. /// fails.
/// </returns> /// </returns>
public bool SetLock() public bool SetLock()
{ {
if (this.locked) if (this.locked)
{ {
CapyEventReporter.EmitEvent(EventLevel.Error, "Lock requested for {0}, but the lock request failed.", args: new[] { this }); CapyEventReporter.EmitEvent(EventLevel.Error, "Lock requested for {0}, but the lock request failed.",
args: new[] { this });
return false; return false;
} }
@ -249,23 +244,12 @@ namespace CapyKit
{ {
if (!this.locked) if (!this.locked)
{ {
CapyEventReporter.EmitEvent(EventLevel.Error, "Lock release requested for {0}, but the lock was already released.", args: new[] { this } ); CapyEventReporter.EmitEvent(EventLevel.Error,
"Lock release requested for {0}, but the lock was already released.", args: new[] { this });
} }
this.locked = false; this.locked = false;
} }
#endregion Methods #endregion Methods
#region Overrides
/// <summary> Returns a string that represents the current object and its lock state. </summary>
/// <returns> A string that represents the current object and its lock state. </returns>
public override string ToString()
{
return string.Format("{0} {1} ({2})", this.typeName, this.index, this.locked ? "Locked" : "Unlocked");
}
#endregion Overrides
}
} }

View file

@ -1,51 +1,41 @@
using System; namespace CapyKit;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CapyKit /// <summary>
/// A object comparer that can accept a lambda expression to compare properties.
/// </summary>
/// <typeparam name="T"> Generic type parameter of the parent object. </typeparam>
/// <typeparam name="U"> Generic type parameter of the property value. </typeparam>
/// <example>
/// using System;
/// using System.Collections.Generic;
/// using System.Linq;
/// class Program
/// {
/// static void Main(string[] args)
/// {
/// var people = new List&lt;Person&gt;
/// {
/// new Person { Name = "Alice", Age = 30 }, new Person { Name = "Bob", Age = 30 }, new
/// Person { Name = "Charlie", Age = 35 }
/// };
/// var comparer = new PropertyComparer&lt;Person, int&gt;(p =&gt; p.Age);
/// var distinctPeople = people.Distinct(comparer).ToList();
/// foreach (var person in distinctPeople)
/// {
/// Console.WriteLine($"{person.Name} - {person.Age}");
/// }
/// }
/// }
/// class Person
/// {
/// public string Name { get; set; }
/// public int Age { get; set; }
/// }
/// </example>
public class PropertyComparer<T, U> : IEqualityComparer<T>
{ {
/// <summary>
/// A object comparer that can accept a lambda expression to compare properties.
/// </summary>
/// <typeparam name="T"> Generic type parameter of the parent object. </typeparam>
/// <typeparam name="U"> Generic type parameter of the property value. </typeparam>
/// <example>
/// using System;
/// using System.Collections.Generic;
/// using System.Linq;
///
/// class Program
/// {
/// static void Main(string[] args)
/// {
/// var people = new List&lt;Person&gt;
/// {
/// new Person { Name = "Alice", Age = 30 }, new Person { Name = "Bob", Age = 30 }, new
/// Person { Name = "Charlie", Age = 35 }
/// };
///
/// var comparer = new PropertyComparer&lt;Person, int&gt;(p =&gt; p.Age);
/// var distinctPeople = people.Distinct(comparer).ToList();
///
/// foreach (var person in distinctPeople)
/// {
/// Console.WriteLine($"{person.Name} - {person.Age}");
/// }
/// }
/// }
///
/// class Person
/// {
/// public string Name { get; set; }
/// public int Age { get; set; }
/// }
/// </example>
public class PropertyComparer<T, U> : IEqualityComparer<T>
{
/// <summary> The expression to retrieve the property. </summary> /// <summary> The expression to retrieve the property. </summary>
private Func<T, U> expression; private readonly Func<T, U> expression;
/// <summary> Constructor. </summary> /// <summary> Constructor. </summary>
/// <param name="expression"> The expression. </param> /// <param name="expression"> The expression. </param>
@ -54,8 +44,10 @@ namespace CapyKit
this.expression = expression; this.expression = expression;
} }
#region IEqualityComparer<T> Members
/// <summary> Determines whether the specified properties are equal. </summary> /// <summary> Determines whether the specified properties are equal. </summary>
/// <param name="x"> The first object of type <typeparamref name="T"/> to compare. </param> /// <param name="x"> The first object of type <typeparamref name="T" /> to compare. </param>
/// <param name="y"> The second object of type <typeparamref name="T" /> to compare. </param> /// <param name="y"> The second object of type <typeparamref name="T" /> to compare. </param>
/// <returns> /// <returns>
/// <see langword="true" /> if the specified objects are equal; otherwise, /// <see langword="true" /> if the specified objects are equal; otherwise,
@ -63,39 +55,39 @@ namespace CapyKit
/// </returns> /// </returns>
public bool Equals(T x, T y) public bool Equals(T x, T y)
{ {
var left = expression.Invoke(x); var left = this.expression.Invoke(x);
var right = expression.Invoke(y); var right = this.expression.Invoke(y);
if (left == null && right == null) if (left == null && right == null)
{ {
return true; return true;
} }
if (left == null ^ right == null)
if ((left == null) ^ (right == null))
{ {
return false; return false;
} }
else
{
return left.Equals(right); return left.Equals(right);
} }
}
/// <summary> Returns a hash code for the specified object. </summary> /// <summary> Returns a hash code for the specified object. </summary>
/// <param name="obj"> /// <param name="obj">
/// The <see cref="T:System.Object" /> for which a hash code is to be returned. /// The <see cref="T:System.Object" /> for which a hash code is to be returned.
/// </param> /// </param>
/// <returns> A hash code for the specified object. </returns> /// <returns> A hash code for the specified object. </returns>
/// /// ###
/// ### <exception cref="T:System.ArgumentNullException"> /// <exception cref="T:System.ArgumentNullException">
/// The type of <paramref name="obj" /> is a reference type and /// The type of <paramref name="obj" /> is a reference type and
/// <paramref name="obj" /> is /// <paramref name="obj" /> is
/// <see langword="null" />. /// <see langword="null" />.
/// </exception> /// </exception>
public int GetHashCode(T obj) public int GetHashCode(T obj)
{ {
var property = expression(obj); var property = this.expression(obj);
return (property == null) ? 0 : property.GetHashCode(); return property == null ? 0 : property.GetHashCode();
}
} }
#endregion
} }