using System.Collections; using System.Collections.Concurrent; using System.Reflection; using SPTarkov.Server.Core.Models.Utils; using SPTarkov.Server.Core.Utils.Json; using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel; namespace SPTarkov.Server.Core.Utils.Cloners; /// /// Not in use at the moment /// /// public class ReflectionsCloner(ISptLogger logger) : ICloner { private static readonly Dictionary MemberInfoCache = new(); private static readonly Dictionary AddMethodInfoCache = new(); private static readonly ConcurrentDictionary _itemPropertyInfoCache = new(); private static readonly ConcurrentDictionary _listPropertyInfoCache = new(); public T? Clone(T? obj) { try { return (T?)Clone(obj, typeof(T)).Result; } catch (Exception e) { logger.Error("Cloning error:", e); return default; } } 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 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; } }