using System; using System.Collections; using System.Collections.Generic; using System.Reflection; namespace FullSerializer.Internal { /// /// 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; Type elementType = GetElementType(storageType); serialized = fsData.CreateList(HintSize(instance)); var serializedList = serialized.AsList; foreach (object 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; // For general strategy, instance may already have items in it. We will try to deserialize into // the existing element. var elementStorageType = GetElementType(storageType); var addMethod = GetAddMethod(storageType); var getMethod = storageType.GetFlattenedMethod("get_Item"); var setMethod = storageType.GetFlattenedMethod("set_Item"); if (setMethod == null) TryClear(storageType, instance); var existingSize = TryGetExistingSize(storageType, instance); var serializedList = data.AsList; for (int i = 0; i < serializedList.Count; ++i) { var itemData = serializedList[i]; object itemInstance = null; if (getMethod != null && i < existingSize) { itemInstance = getMethod.Invoke(instance, new object[] { i }); } // note: We don't fail the entire deserialization even if the item failed var itemResult = Serializer.TryDeserialize(itemData, elementStorageType, ref itemInstance); result.AddMessages(itemResult); if (itemResult.Failed) continue; if (setMethod != null && i < existingSize) { setMethod.Invoke(instance, new object[] { i, itemInstance }); } else { addMethod.Invoke(instance, new object[] { itemInstance }); } } 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(); Type 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). Type collectionInterface = fsReflectionUtility.GetInterface(type, typeof(ICollection<>)); if (collectionInterface != null) { MethodInfo 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"); } } }