using System.Collections; using System.Collections.Concurrent; using System.Reflection; using Core.Models.Utils; using Core.Utils.Json; using LogLevel = Core.Models.Spt.Logging.LogLevel; namespace Core.Utils.Cloners; /** * Not in use at the moment */ public class ReflectionsCloner(ISptLogger logger) : ICloner { private static Dictionary MemberInfoCache = new(); private static Dictionary AddMethodInfoCache = new(); public T? Clone(T? obj) { return (T?) Clone(obj, typeof(T)).Result; } public async Task Clone(object? obj, Type objectType) { // if its null, primitive, enum or string, just return the object if (obj == null) { return obj; } if (objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(Nullable<>)) { objectType = Nullable.GetUnderlyingType(objectType); } if (objectType.IsPrimitive || objectType.IsEnum || obj is string) { return obj; } if (objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(ListOrT<>)) { return await HandleSpecialClones(obj, objectType); } var result = Activator.CreateInstance(objectType); if (obj is IList listToClone) { foreach (var toClone in listToClone) { var cloned = await Clone(toClone, toClone.GetType()); (result as IList).Add(cloned); } } else if (obj is IDictionary dictionaryToClone) { foreach (DictionaryEntry entryToClone in dictionaryToClone) { var clonedKey = await Clone(entryToClone.Key, entryToClone.Key.GetType()); var clonedValue = await Clone(entryToClone.Value, entryToClone.Value.GetType()); (result as IDictionary).Add(clonedKey, clonedValue); } } else if (objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(HashSet<>)) { if (!AddMethodInfoCache.TryGetValue(objectType, out var addMethodInfo)) { addMethodInfo = objectType.GetMethod("Add", BindingFlags.Instance | BindingFlags.Public); while (!AddMethodInfoCache.TryAdd(objectType, addMethodInfo)) ; } var toCloneEnumerable = (IEnumerable) obj; foreach (var toClone in toCloneEnumerable) { try { var cloned = await Clone(toClone, toClone.GetType()); addMethodInfo.Invoke(result, [cloned]); } catch (Exception e) { Console.WriteLine(e); throw; } } } else if (objectType.IsClass) { if (!MemberInfoCache.TryGetValue(objectType, out var memberInfos)) { memberInfos = objectType.GetMembers(BindingFlags.Public | BindingFlags.Instance); while (!MemberInfoCache.TryAdd(objectType, memberInfos)) ; } foreach (var member in memberInfos) { try { switch (member) { case PropertyInfo propertyInfo: var propertyValue = propertyInfo.GetValue(obj, null); var propertyCloned = await Clone(propertyValue, propertyInfo.PropertyType); propertyInfo.SetValue(result, propertyCloned, null); break; case FieldInfo fieldInfo: var fieldValue = fieldInfo.GetValue(obj); var fieldCloned = await Clone(fieldValue, fieldInfo.FieldType); fieldInfo.SetValue(result, fieldCloned); break; case MemberInfo: break; default: if (logger.IsLogEnabled(LogLevel.Debug)) { logger.Debug($"Unknown member type {member.Name} {member.MemberType}"); } break; } } catch (Exception e) { Console.WriteLine(e); throw; } } } else { if (logger.IsLogEnabled(LogLevel.Debug)) logger.Debug($"Clone of type {objectType} is not supported"); } return result; } private static ConcurrentDictionary _itemPropertyInfoCache = new(); private static ConcurrentDictionary _listPropertyInfoCache = new(); private async Task HandleSpecialClones(object obj, Type objectType) { if (objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(ListOrT<>)) { var clone = Activator.CreateInstance(objectType, true); var type = objectType.GetGenericArguments()[0]; if (!_itemPropertyInfoCache.TryGetValue(type, out var item)) { item = objectType.GetProperty("Item", BindingFlags.Public | BindingFlags.Instance); while (!_itemPropertyInfoCache.TryAdd(type, item)) ; } if (!_listPropertyInfoCache.TryGetValue(type, out var list)) { list = objectType.GetProperty("List", BindingFlags.Public | BindingFlags.Instance); while (!_listPropertyInfoCache.TryAdd(type, list)) ; } item.GetSetMethod(true).Invoke(clone, [await Clone(item.GetValue(obj), item.PropertyType)]); list.GetSetMethod(true).Invoke(clone, [await Clone(list.GetValue(obj), list.PropertyType)]); return clone; } return null; } }