// 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;
}
}