using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using JetBrains.Annotations; using Kyoo.Models.Attributes; namespace Kyoo { /// /// A class containing helper methods to merge objects. /// public static class Merger { /// /// Merge two lists, can keep duplicates or remove them. /// /// The first enumerable to merge /// The second enumerable to merge, if items from this list are equals to one from the first, they are not kept /// Equality function to compare items. If this is null, duplicated elements are kept /// The two list merged as an array public static T[] MergeLists(IEnumerable first, IEnumerable second, Func isEqual = null) { if (first == null) return second.ToArray(); if (second == null) return first.ToArray(); if (isEqual == null) return first.Concat(second).ToArray(); List list = first.ToList(); return list.Concat(second.Where(x => !list.Any(y => isEqual(x, y)))).ToArray(); } /// /// Set every fields of first to those of second. Ignore fields marked with the attribute /// At the end, the OnMerge method of first will be called if first is a /// /// The object to assign /// The object containing new values /// Fields of T will be used /// public static T Assign(T first, T second) { Type type = typeof(T); IEnumerable properties = type.GetProperties() .Where(x => x.CanRead && x.CanWrite && Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null); foreach (PropertyInfo property in properties) { object value = property.GetValue(second); property.SetValue(first, value); } if (first is IOnMerge merge) merge.OnMerge(second); return first; } /// /// Set every default values of first to the value of second. ex: {id: 0, slug: "test"}, {id: 4, slug: "foo"} -> {id: 4, slug: "test"}. /// At the end, the OnMerge method of first will be called if first is a /// /// The object to complete /// Missing fields of first will be completed by fields of this item. If second is null, the function no-op. /// Filter fields that will be merged /// Fields of T will be completed /// /// If first is null public static T Complete([NotNull] T first, [CanBeNull] T second, Func where = null) { if (first == null) throw new ArgumentNullException(nameof(first)); if (second == null) return first; Type type = typeof(T); IEnumerable properties = type.GetProperties() .Where(x => x.CanRead && x.CanWrite && Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null); if (where != null) properties = properties.Where(where); foreach (PropertyInfo property in properties) { object value = property.GetValue(second); object defaultValue = property.PropertyType.IsValueType ? Activator.CreateInstance(property.PropertyType) : null; if (value?.Equals(defaultValue) == false && value != property.GetValue(first)) property.SetValue(first, value); } if (first is IOnMerge merge) merge.OnMerge(second); return first; } /// /// An advanced function. /// This will set missing values of to the corresponding values of . /// Enumerable will be merged (concatenated). /// At the end, the OnMerge method of first will be called if first is a . /// /// The object to complete /// Missing fields of first will be completed by fields of this item. If second is null, the function no-op. /// Fields of T will be merged /// public static T Merge(T first, T second) { if (first == null) return second; if (second == null) return first; Type type = typeof(T); IEnumerable properties = type.GetProperties() .Where(x => x.CanRead && x.CanWrite && Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null); foreach (PropertyInfo property in properties) { object oldValue = property.GetValue(first); object newValue = property.GetValue(second); object defaultValue = property.PropertyType.IsValueType ? Activator.CreateInstance(property.PropertyType) : null; if (oldValue?.Equals(defaultValue) != false) property.SetValue(first, newValue); else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType) && property.PropertyType != typeof(string)) { Type enumerableType = Utility.GetGenericDefinition(property.PropertyType, typeof(IEnumerable<>)) .GenericTypeArguments .First(); property.SetValue(first, Utility.RunGenericMethod( typeof(Utility), nameof(MergeLists), enumerableType, oldValue, newValue, null)); } } if (first is IOnMerge merge) merge.OnMerge(second); return first; } /// /// Set every fields of to the default value. /// /// The object to nullify /// Fields of T will be nullified /// public static T Nullify(T obj) { Type type = typeof(T); foreach (PropertyInfo property in type.GetProperties()) { if (!property.CanWrite) continue; object defaultValue = property.PropertyType.IsValueType ? Activator.CreateInstance(property.PropertyType) : null; property.SetValue(obj, defaultValue); } return obj; } } }