Наша сборка Qt VS Tools
giy
2022-06-13 175679ae608f0b295d761588d332f635b21bdf30
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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt VS Tools.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
 
namespace QtVsTools.Json
{
    /// <summary>
    /// Classes in an hierarchy derived from Serializable<T> represent objects that can be mapped
    /// to and from JSON data using the DataContractJsonSerializer class. When deserializing, the
    /// class hierarchy will be searched for the derived class best suited to the data.
    /// </summary>
    /// <typeparam name="TBase">Base of the class hierarchy</typeparam>
    [DataContract]
    abstract class Serializable<TBase> :
        Prototyped<TBase>,
        IDeferrable<TBase>,
        IDeferredObjectContainer
        where TBase : Serializable<TBase>
    {
        #region //////////////////// Prototype ////////////////////////////////////////////////////
 
        protected Serializer Serializer { get; set; }
 
        protected Serializable()
        { }
 
        protected sealed override void InitializePrototype()
        {
            System.Diagnostics.Debug.Assert(IsPrototype);
 
            // Create serializer for this particular type
            Serializer = Serializer.Create(GetType());
        }
 
        /// <summary>
        /// Check if this class is suited as target type for deserialization, based on the
        /// information already deserialized. Prototypes of derived classes will override this to
        /// implement the local type selection rules.
        /// </summary>
        /// <param name="that">Object containing the data deserialized so far</param>
        /// <returns>
        ///     true  ::= class is suitable and can be used as target type for deserialization
        ///
        ///     false ::= class is not suitable for deserialization
        ///
        ///     null  ::= a derived class of this class might be suitable; search for target type
        ///               should be expanded to include all classes derived from this class
        /// </returns>
        ///
        protected virtual bool? IsCompatible(TBase that)
        {
            System.Diagnostics.Debug.Assert(IsPrototype);
 
            return null;
        }
 
        /// <summary>
        /// Check if this class is marked with the [SkipDeserialization] attribute, which signals
        /// that deserialization of this class is to be skipped while traversing the class
        /// hierarchy looking for a suitable target type for deserialization.
        /// </summary>
        ///
        bool SkipDeserialization
        {
            get
            {
                System.Diagnostics.Debug.Assert(IsPrototype);
 
                return GetType()
                    .GetCustomAttributes(typeof(SkipDeserializationAttribute), false).Any();
            }
        }
 
        /// <summary>
        /// Perform a deferred deserialization based on this class hierarchy.
        /// </summary>
        /// <param name="jsonData">Data to deserialize</param>
        /// <returns>Deserialized object</returns>
        ///
        TBase IDeferrable<TBase>.Deserialize(IJsonData jsonData)
        {
            System.Diagnostics.Debug.Assert(this == BasePrototype);
 
            return DeserializeClassHierarchy(null, jsonData);
        }
 
        #endregion //////////////////// Prototype /////////////////////////////////////////////////
 
 
        #region //////////////////// Deferred Objects /////////////////////////////////////////////
 
        List<IDeferredObject> deferredObjects = null;
        List<IDeferredObject> DeferredObjects
        {
            get
            {
                Atomic(() => deferredObjects == null,
                    () => deferredObjects = new List<IDeferredObject>());
 
                return deferredObjects;
            }
        }
 
        public IEnumerable<IDeferredObject> PendingObjects
        {
            get
            {
                return DeferredObjects.Where(x => !x.HasData);
            }
        }
 
        void IDeferredObjectContainer.Add(IDeferredObject item)
        {
            ThreadSafe(() => DeferredObjects.Add(item));
        }
 
        protected void Add(IDeferredObject item)
        {
            ((IDeferredObjectContainer)this).Add(item);
        }
 
        #endregion //////////////////// Deferred Objects //////////////////////////////////////////
 
 
        /// <summary>
        /// Initialize new instance. Derived classes override this to implement their own
        /// initializations.
        /// </summary>
        ///
        protected virtual void InitializeObject(object initArgs)
        { }
 
        /// <summary>
        /// Serialize object.
        /// </summary>
        /// <returns>Raw JSON data</returns>
        ///
        public byte[] Serialize()
        {
            return ThreadSafe(() => Prototype.Serializer.Serialize(this).GetBytes());
        }
 
        /// <summary>
        /// Deserialize object using this class hierarchy. After selecting the most suitable derived
        /// class as target type and deserializing an instance of that class, any deferred objects
        /// are also deserialized using their respective class hierarchies.
        /// </summary>
        /// <param name="initArgs">Additional arguments required for object initialization</param>
        /// <param name="data">Raw JSON data</param>
        /// <returns>Deserialized object, or null if deserialization failed</returns>
        ///
        public static TBase Deserialize(object initArgs, byte[] data)
        {
            TBase obj = DeserializeClassHierarchy(initArgs, Serializer.Parse(data));
            if (obj == null)
                return null;
 
            var toDo = new Queue<IDeferredObjectContainer>();
            if (obj.PendingObjects.Any())
                toDo.Enqueue(obj);
 
            while (toDo.Count > 0) {
                var container = toDo.Dequeue();
                foreach (var defObj in container.PendingObjects) {
                    defObj.Deserialize();
                    var subContainer = defObj.Object as IDeferredObjectContainer;
                    if (subContainer != null && subContainer.PendingObjects.Any())
                        toDo.Enqueue(subContainer);
                }
            }
            return obj;
        }
 
        public static TBase Deserialize(byte[] data)
        {
            return Deserialize(null, data);
        }
 
        /// <summary>
        /// Traverse this class hierarchy looking for the most suitable derived class that can be
        /// used as target type for the deserialization of the JSON data provided.
        /// </summary>
        /// <param name="initArgs">Additional arguments required for object initialization</param>
        /// <param name="jsonData">Parsed JSON data</param>
        /// <returns>Deserialized object, or null if deserialization failed</returns>
        ///
        protected static TBase DeserializeClassHierarchy(object initArgs, IJsonData jsonData)
        {
            //  PSEUDOCODE:
            //
            //  Nodes to visit := base of class hierarchy.
            //  While there are still nodes to visit
            //      Current node ::= Extract next node to visit.
            //      Tentative object := Deserialize using current node as target type.
            //      If deserialization failed
            //          Skip branch, continue (with next node, if any).
            //      Else
            //          Test compatibility of current node with tentative object.
            //          If not compatible
            //              Skip branch, continue (with next node, if any).
            //          If compatible
            //              If leaf node
            //                  Found suitable node!!
            //                  Return tentative object as final result of deserialization.
            //              Else
            //                  Save tentative object as last sucessful deserialization.
            //                  Add child nodes to the nodes to visit.
            //          If inconclusive (i.e. a child node might be compatible)
            //              Add child nodes to the nodes to visit.
            //  If no suitable node was found
            //      Return last sucessful deserialization as final result of deserialization.
 
            lock (BaseClass.Prototype.CriticalSection) {
 
                var toDo = new Queue<SubClass>(new[] { BaseClass });
                TBase lastCompatibleObj = null;
 
                // Traverse class hierarchy tree looking for a compatible leaf node
                // i.e. compatible class without any sub-classes
                while (toDo.Count > 0) {
                    var subClass = toDo.Dequeue();
 
                    // Try to deserialize as sub-class
                    TBase tryObj;
                    if (jsonData.IsEmpty())
                        tryObj = CreateInstance(subClass.Type);
                    else
                        tryObj = subClass.Prototype.Serializer.Deserialize(jsonData) as TBase;
 
                    if (tryObj == null)
                        continue; // Not deserializable as this type
 
                    tryObj.InitializeObject(initArgs);
 
                    // Test compatbility
                    var isCompatible = subClass.Prototype.IsCompatible(tryObj);
 
                    if (isCompatible == false) {
                        // Incompatible
                        continue;
 
                    } else if (isCompatible == true) {
                        // Compatible
 
                        if (!subClass.SubTypes.Any())
                            return tryObj; // Found compatible leaf node!
 
                        // Non-leaf node; continue searching
                        lastCompatibleObj = tryObj;
                        PotentialSubClasses(subClass, tryObj)
                            .ForEach(x => toDo.Enqueue(x));
                        continue;
 
                    } else {
                        // Maybe has compatible derived class
 
                        if (subClass.SubTypes.Any()) {
                            // Non-leaf node; continue searching
                            PotentialSubClasses(subClass, tryObj)
                                .ForEach(x => toDo.Enqueue(x));
                        }
                        continue;
                    }
                }
 
                // No compatible leaf node found
                // Use last successful (non-leaf) deserializtion, if any
                return lastCompatibleObj;
            }
        }
 
        /// <summary>
        /// Get list of sub-classes of a particular class that are potentially suitable to the
        /// deserialized data. Sub-classes marked with the [SkipDeserialization] attribute will not
        /// be returned; their own sub-sub-classes will be tested for compatibility and returned in
        /// case they are potentially suitable (i.e.: IsCompatible == true || IsCompatible == null)
        /// </summary>
        /// <param name="subClass">Class whose sub-classes are to be tested</param>
        /// <param name="tryObj">Deserialized data</param>
        /// <returns>List of sub-classes that are potentially suitable for deserialization</returns>
        static List<SubClass> PotentialSubClasses(SubClass subClass, TBase tryObj)
        {
            if (subClass == null || tryObj == null)
                return new List<SubClass>();
 
            var potential = new List<SubClass>();
            var toDo = new Queue<SubClass>(subClass.SubClasses);
            while (toDo.Count > 0) {
                subClass = toDo.Dequeue();
 
                if (subClass.Prototype.IsCompatible(tryObj) == false)
                    continue;
 
                if (subClass.Prototype.SkipDeserialization && subClass.SubClasses.Any()) {
                    foreach (var subSubClass in subClass.SubClasses)
                        toDo.Enqueue(subSubClass);
 
                    continue;
                }
 
                potential.Add(subClass);
            }
 
            return potential;
        }
    }
 
    class SkipDeserializationAttribute : Attribute
    { }
}