chr
2026-04-05 fe750b791d5b517cc4e9bc8e99a9a75139a0cfba
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
//            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
{
    /// <summary> Serializer implementation for Resources. </summary>
    internal class ResourceSerializer : TapSerializerPlugin
    {
        /// <summary>
        /// True if there was an change caused by a mismatch of resource names in the tesplan and names in the bench settings
        /// </summary>
        internal bool TestPlanChanged { get; set; }
 
        /// <summary> The order of this serializer. </summary>
        public override double Order { get { return 1; } }
 
        static XName sourceName = "Source"; 
        
        /// <summary> Deserialization implementation. </summary>
        public override bool Deserialize( XElement elem, ITypeData _t, Action<object> 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<object, bool> 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<object> checkRentry = new HashSet<object>();
        
        /// <summary> Serialization implementation. </summary>
        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;
        }
    }
 
}