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