| /****************************************************************************  | 
| **  | 
| ** 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  | 
|     { }  | 
| }  |