using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using Unity.VisualScripting.FullSerializer.Internal;
namespace Unity.VisualScripting.FullSerializer
{
///
/// Provides serialization support for anything which extends from
/// `IEnumerable` and has an `Add` method.
///
public class fsIEnumerableConverter : fsConverter
{
public override bool CanProcess(Type type)
{
if (typeof(IEnumerable).IsAssignableFrom(type) == false)
{
return false;
}
return GetAddMethod(type) != null;
}
public override object CreateInstance(fsData data, Type storageType)
{
return fsMetaType.Get(Serializer.Config, storageType).CreateInstance();
}
public override fsResult TrySerialize(object instance_, out fsData serialized, Type storageType)
{
var instance = (IEnumerable)instance_;
var result = fsResult.Success;
var elementType = GetElementType(storageType);
serialized = fsData.CreateList(HintSize(instance));
var serializedList = serialized.AsList;
foreach (var item in instance)
{
fsData itemData;
// note: We don't fail the entire deserialization even if the
// item failed
var itemResult = Serializer.TrySerialize(elementType, item, out itemData);
result.AddMessages(itemResult);
if (itemResult.Failed)
{
continue;
}
serializedList.Add(itemData);
}
// Stacks iterate from back to front, which means when we deserialize
// we will deserialize the items in the wrong order, so the stack
// will get reversed.
if (IsStack(instance.GetType()))
{
serializedList.Reverse();
}
return result;
}
private bool IsStack(Type type)
{
return type.Resolve().IsGenericType &&
type.Resolve().GetGenericTypeDefinition() == typeof(Stack<>);
}
public override fsResult TryDeserialize(fsData data, ref object instance_, Type storageType)
{
var instance = (IEnumerable)instance_;
var result = fsResult.Success;
if ((result += CheckType(data, fsDataType.Array)).Failed)
{
return result;
}
// LAZLO/LUDIQ: Changes to default behaviour.
// - Do not try to serialize into existing element; always clear and add
// (more reliable, compatible with custom indexers)
// - If the type is a list, add a default element on failure to prevent
// messing up the order.
// - Commented out that last change.
var elementStorageType = GetElementType(storageType);
var addMethod = GetAddMethod(storageType);
TryClear(storageType, instance);
var serializedList = data.AsList;
for (var i = 0; i < serializedList.Count; ++i)
{
var itemData = serializedList[i];
object itemInstance = null;
var itemResult = Serializer.TryDeserialize(itemData, elementStorageType, ref itemInstance);
result.AddMessages(itemResult);
if (itemResult.Succeeded)
{
addMethod.Invoke(instance, new[] { itemInstance });
}
// else
// {
// if (typeof(IList).IsAssignableFrom(storageType))
// {
// addMethod.Invoke(instance, new[] { elementStorageType.Default() });
// }
// else
// {
// continue;
// }
// }
}
return result;
}
private static int HintSize(IEnumerable collection)
{
if (collection is ICollection)
{
return ((ICollection)collection).Count;
}
return 0;
}
///
/// Fetches the element type for objects inside of the collection.
///
private static Type GetElementType(Type objectType)
{
if (objectType.HasElementType)
{
return objectType.GetElementType();
}
var enumerableList = fsReflectionUtility.GetInterface(objectType, typeof(IEnumerable<>));
if (enumerableList != null)
{
return enumerableList.GetGenericArguments()[0];
}
return typeof(object);
}
private static void TryClear(Type type, object instance)
{
var clear = type.GetFlattenedMethod("Clear");
if (clear != null)
{
clear.Invoke(instance, null);
}
}
private static int TryGetExistingSize(Type type, object instance)
{
var count = type.GetFlattenedProperty("Count");
if (count != null)
{
return (int)count.GetGetMethod().Invoke(instance, null);
}
return 0;
}
private static MethodInfo GetAddMethod(Type type)
{
// There is a really good chance the type will extend ICollection{},
// which will contain the add method we want. Just doing
// type.GetFlattenedMethod() may return the incorrect one -- for
// example, with dictionaries, it'll return Add(TKey, TValue), and we
// want Add(KeyValuePair).
var collectionInterface = fsReflectionUtility.GetInterface(type, typeof(ICollection<>));
if (collectionInterface != null)
{
var add = collectionInterface.GetDeclaredMethod("Add");
if (add != null)
{
return add;
}
}
// Otherwise try and look up a general Add method.
return
type.GetFlattenedMethod("Add") ??
type.GetFlattenedMethod("Push") ??
type.GetFlattenedMethod("Enqueue");
}
}
}