// 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.Collections;
namespace OpenTap.Plugins
{
/// Serializer implementation for Resources.
internal class ResourceSerializer : TapSerializerPlugin
{
///
/// True if there was an change caused by a mismatch of resource names in the tesplan and names in the bench settings
///
internal bool TestPlanChanged { get; set; }
/// The order of this serializer.
public override double Order { get { return 1; } }
static XName sourceName = "Source";
/// Deserialization implementation.
public override bool Deserialize( XElement elem, ITypeData _t, Action setter)
{
// fastest and most obvious checks first.
if (elem.HasElements) return false;
var t = _t.AsTypeData()?.Type;
if (t == null) return false;
string src = null;
{
var srcAttribute = elem.Attribute(sourceName);
if (srcAttribute != null)
src = srcAttribute.Value;
}
// null and white-space has different meaning here.
// "" means explicitly no source collection, while null means no known source.
if (src == "")
return false;
if (t.DescendsTo(typeof(IResource)) ||
t.DescendsTo(typeof(Connection)) && ComponentSettingsList.HasContainer(t))
{
var content = elem.Value.Trim();
// If there is no type matching the exact requested type.
// try looking one up that matches the property type.
// propertyType is an attempty to calculate the current target type.
// it might be null if it cannot be determined.
var propertyType = (Serializer.SerializerStack.Skip(1).FirstOrDefault() as ObjectSerializer)
?.CurrentMember?.TypeDescriptor?.AsTypeData()?.Type;
if (propertyType == null)
{
propertyType = (Serializer.SerializerStack.Skip(1).FirstOrDefault() as CollectionSerializer)
?.CurrentElementType;
}
Serializer.DeferLoad(() =>
{
// we need to load this asynchronously to avoid recursively
// serializing another ComponentSettings.
if (string.IsNullOrWhiteSpace(content))
{
setter(null);
return;
}
string getName(object o)
{
if (o is Connection con)
return con.Name;
if (o is IResource res)
return res.Name;
return "";
}
// The following are the priorities for resource resolution.
// 1. matching name and exact type.
// 2. matching name and compatible type. (info message emitted)
// 3. matching type. (errors emitted)
// 4. defaulting to (errors emitted) compatible type.
var obj = fetchObject(t, propertyType ?? t, o => getName(o).Trim() == content, src);
if (obj != null)
{
var name = getName(obj).Trim();
if (name != content && !string.IsNullOrWhiteSpace(content))
{
TestPlanChanged = true;
var msg = $"Missing '{content}'. Using '{name}' instead.";
if (elem.Parent.Element("Name") != null)
msg =
$"Missing '{content}' used by '{elem.Parent.Element("Name").Value}.{elem.Name}. Using '{name}' instead.'";
Serializer.PushError(elem, msg);
}else if (obj.GetType().DescendsTo(t) == false)
{
Serializer.PushMessage(elem,
$"Selected resource '{getName(obj)}' of type {obj.GetType().GetDisplayAttribute().GetFullName()} instead of declared type {t.GetDisplayAttribute().GetFullName()}.");
}
setter(obj);
}
else
{
TestPlanChanged = true;
var msg = $"Missing '{content}'.";
if (elem.Parent.Element("Name") != null)
msg =
$"Missing '{content}' used by '{elem.Parent.Element("Name").Value}.{elem.Name}'";
Serializer.PushError(elem, msg);
}
});
return true;
}
return false;
}
object fetchObject(Type exactType, Type compatibleType, Func test, string src)
{
IList objects = null;
if(src != null)
{
var containerType = PluginManager.LocateType(src);
if(containerType != null)
{
objects = (IList)ComponentSettings.GetCurrent(containerType);
}
}
if(objects == null)
objects = ComponentSettingsList.GetContainer(exactType);
object exactMatch = null;
object compatibleMatch = null;
object exactTypeMatch = null;
foreach (var obj in objects)
{
if (obj == null) continue;
bool matchName = test(obj);
bool matchType = obj.GetType().DescendsTo(exactType);
if (matchName)
{
if (matchType)
{
exactMatch = obj;
return exactMatch;
}
if (exactType != compatibleType && obj.GetType().DescendsTo(compatibleType))
{
compatibleMatch = obj;
}
}
else
{
if (matchType)
{
exactTypeMatch = obj;
}
}
}
return exactMatch ?? compatibleMatch ?? exactTypeMatch;
}
HashSet checkRentry = new HashSet();
/// Serialization implementation.
public override bool Serialize( XElement elem, object obj, ITypeData expectedType)
{
if (obj == null) return false;
Type type = obj.GetType();
if((obj is IResource && ComponentSettingsList.HasContainer(type)) || obj is Connection)
{
// source was set by something else. Assume it can deserialize as well.
if (false == string.IsNullOrEmpty(elem.Attribute("Source")?.Value as string)) return false;
// If the next thing on the stack is a CollectionSerializer, it means that we are deserializing a ComponentSettingsList.
// but if it is a ObjectSerializer it is probably a nested(stacked) resource property.
var prevSerializer = Serializer.SerializerStack.FirstOrDefault(x => x is CollectionSerializer || x is ObjectSerializer);
var collectionSerializer = prevSerializer as CollectionSerializer;
if (collectionSerializer != null && collectionSerializer.ComponentSettingsSerializing.Contains(obj))
{
if (checkRentry.Contains(obj)) return false;
checkRentry.Add(obj);
try
{
var result = Serializer.Serialize(elem, obj, expectedType);
if (result)
elem.SetAttributeValue(sourceName, ""); // set src to "" to show that it should not be deserialized by reference.
return result;
}
finally
{
checkRentry.Remove(obj);
}
}
var container = ComponentSettingsList.GetContainer(type);
var index = container.IndexOf(obj);
if (index == -1)
{
if (container is IComponentSettingsList lst)
{
if (lst.GetRemovedAliveResources().Contains(obj))
{ // skip serializing if the referenced instrument has been deleted.
elem.Remove();
return true;
}
}
// serialize it normally / in-place.
return false;
}
if (index != -1)
{
if (obj is Connection con)
elem.Value = con.Name ?? index.ToString();
else if (obj is IResource res)
elem.Value = res.Name ?? index.ToString();
elem.SetAttributeValue(sourceName, container.GetType().FullName);
}
// important to return true, otherwise it will serialize as a new value.
return true;
}
return false;
}
}
}