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