// Copyright Keysight Technologies 2012-2019 // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, you can obtain one at http://mozilla.org/MPL/2.0/. using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; using System.Globalization; using System.Collections; namespace OpenTap.Plugins { /// Serializer implementation for Collections. internal class CollectionSerializer : TapSerializerPlugin, IConstructingSerializer { /// Order of this serializer. public override double Order { get; } = 1; /// Current serializing element type. public Type CurrentElementType { get; private set; } /// Deserialization implementation. public override bool Deserialize( XElement element, ITypeData _t, Action setResult) { object prevobj = this.Object; var prevElementType = CurrentElementType; try { var t = (_t as TypeData)?.Type; if (t == null || !t.DescendsTo(typeof(IEnumerable)) || t == typeof(string)) return false; IEnumerable finalValues = null; Type genericType = t.GetEnumerableElementType() ?? typeof(object); CurrentElementType = genericType; if (t.IsArray == false && t.HasInterface() && t.GetConstructor(Type.EmptyTypes) != null && genericType.IsValueType == false) { finalValues = (IList) Activator.CreateInstance(t); } if (finalValues == null) finalValues = new List(); var prevSetResult = setResult; setResult = x => { // ensure that IDeserializedCallback is going to get used. prevSetResult(x); var deserializedHandler = x as IDeserializedCallback; if (deserializedHandler != null) { Serializer.DeferLoad(() => { try { deserializedHandler.OnDeserialized(); } catch (Exception e) { Log.Warning("Exception caught while handling OnDeserialized"); Log.Debug(e); } }); } }; bool tryGetFactory(out Func ctor) { var os = Serializer.SerializerStack .OfType() .FirstOrDefault(); if (os?.CurrentMember is IMemberData member && member.GetAttribute() is {} fac && member.TypeDescriptor.DescendsTo(t)) { if (member is MemberData && os.Object != null) { ctor = () => (IList)FactoryAttribute.Create(os.Object, fac); return true; } if (member is IParameterMemberData pmem) { ctor = () => { if (!FactoryAttribute.TryCreateFromMember(pmem, fac, out var o)) throw new Exception( $"Cannot create object of type '{pmem.TypeDescriptor.Name}' using factory '{fac.FactoryMethodName}'."); if (o is not IList lst) throw new Exception( $"Object constructed from factory '{fac.FactoryMethodName}' is not a list."); return lst; }; return true; } } ctor = null; return false; } if (element.IsEmpty) { try { var os = Serializer.SerializerStack .OfType() .FirstOrDefault(); if (!_t.CanCreateInstance && !t.IsArray && tryGetFactory(out var f)) { setResult(f()); } else if (t.IsArray) { setResult(Array.CreateInstance(t.GetElementType(), 0)); } else if (t.IsInterface) { setResult(null); } else { setResult(Activator.CreateInstance(t)); } return true; } catch (MemberAccessException) { // No or private zero-arg constructor. // Cannot create instance - let it be null. } return false; } IEnumerable values = null; if (genericType.IsNumeric() && element.HasElements == false) { var str = element.Value; var parser = new NumberFormatter(CultureInfo.InvariantCulture); var values2 = parser.Parse(str); finalValues = values2.CastTo(genericType); if (t == typeof(IEnumerable<>).MakeGenericType(genericType) || finalValues.GetType() == t) { setResult(finalValues); return true; } } { if (!_t.CanCreateInstance && !t.IsArray) { var os = Serializer.SerializerStack.OfType().FirstOrDefault(); var mem = os?.CurrentMember; if (mem == null) throw new Exception("Unable to get member list"); /* First check if there is a factory we can use. */ if (tryGetFactory(out var f)) { values = f(); this.Object = values; } else /* otherwise try to update in place */ { // the data has to be updated in-place. var val = ((IEnumerable)mem.GetValue(os.Object)).Cast(); var elems = element.Elements(); if (elems.Count() != val.Count()) { Log.Warning("Deserialized unbalanced list."); } foreach (var (elem, obj) in elems.Pairwise(val)) { if (!os.TryDeserializeObject(elem, TypeData.GetTypeData(obj), o => { }, obj, true)) return false; } return true; } } if(this.Object != values) this.Object = finalValues; if (finalValues is ICombinedNumberSequence seq) finalValues = seq.Cast().ToArray(); var vals = (IList) finalValues; foreach (var node2 in element.Elements()) { try { int index = vals.Count; vals.Add(null); if (node2.IsEmpty == false || node2.HasAttributes) // Assuming that null is allowed. { if (!Serializer.Deserialize(node2, x => vals[index] = x, t: genericType)) // Serialization of an element failed, so remove the placeholder. vals.RemoveAt(vals.Count - 1); } } catch (Exception e) { vals.RemoveAt(vals.Count - 1); Log.Error(e.Message); Log.Debug(e); continue; } } } if (t.IsArray) { int elementCount = finalValues.Cast().Count(); values = (IList) Activator.CreateInstance(t, elementCount); Serializer.DeferLoad(() => { // Previous deserialization might have been deferred, so we have to defer again. var lst = (IList) values; int i = 0; foreach (var item in finalValues) lst[i++] = item; setResult(values); }); } else if (!_t.CanCreateInstance && !t.IsArray && tryGetFactory(out _)) { var lst = (IList)values; foreach (var item in finalValues) lst.Add(item); values = lst; } else if (t.DescendsTo(typeof(System.Collections.ObjectModel.ReadOnlyCollection<>))) { var lst = (IList) Activator.CreateInstance(typeof(List<>).MakeGenericType(genericType)); foreach (var item in finalValues) lst.Add(item); values = (IList) Activator.CreateInstance(t, lst); } else if (t.HasInterface()) { if (finalValues.GetType() == t) { values = (IList) finalValues; } else { values = (IList) Activator.CreateInstance(t); if (values is ComponentSettings) { // ComponentSettings must be loaded now. foreach (var item in finalValues) ((IList) values).Add(item); } else { foreach (var val in finalValues) ((IList) values).Add(val); } } } else if (finalValues is IList lst) { values = lst; } else if (finalValues is IEnumerable en) { var lst2 = new List(); foreach (var value in en.Cast()) lst2.Add(value); values = lst2; } else { throw new Exception("Unable to deserialize list"); } if (values.GetType().DescendsTo(t) == false) { try { dynamic newvalues = Activator.CreateInstance(t); if (newvalues is IDictionary) { foreach (dynamic x in values) newvalues.Add(x.Key, x.Value); // x is KeyValuePair. } else { foreach (dynamic x in values) newvalues.Add(x); } values = newvalues; } catch (Exception) { Log.Warning(element, "Unable to deserialize enumerable type '{0}'", t.Name); } } setResult(values); return true; } finally { this.Object = prevobj; CurrentElementType = prevElementType; } } static readonly XName Element = "Element"; internal HashSet ComponentSettingsSerializing = new HashSet(); /// Serialization implementation. public override bool Serialize( XElement elem, object sourceObj, ITypeData expectedType) { if (sourceObj is IEnumerable == false || sourceObj is string) return false; IEnumerable sourceEnumerable = (IEnumerable)sourceObj; Type type = sourceObj.GetType(); Type genericTypeArg = type.GetEnumerableElementType(); if (genericTypeArg == null) return false; if (genericTypeArg.IsNumeric()) { var parser = new NumberFormatter(CultureInfo.InvariantCulture) {UseRanges = false}; elem.Value = parser.FormatRange(sourceEnumerable); return true; } object prevObj = this.Object; try { this.Object = sourceObj; bool isComponentSettings = sourceObj is ComponentSettings; foreach (object obj in sourceEnumerable) { var step = new XElement(Element); // We need to add the step to the document immediately so each serializer call // has access to the element's parents. elem.Add(step); if (obj != null) { type = obj.GetType(); step.Name = TapSerializer.TypeToXmlString(type); if (isComponentSettings) ComponentSettingsSerializing.Add(obj); try { Serializer.Serialize(step, obj, expectedType: TypeData.FromType(genericTypeArg)); } catch { // Remove the element from the document in case serialization fails step.Remove(); throw; } finally { if (isComponentSettings) ComponentSettingsSerializing.Remove(obj); } } } } finally { this.Object = prevObj; } return true; } public object Object { get; private set; } public IMemberData CurrentMember => null; } }