RaspberryIO/Unosquare.Swan.Lite/Extensions.cs
2019-12-04 17:10:06 +01:00

305 lines
11 KiB
C#

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