/[svn]/libgig/trunk/src/Serialization.h
ViewVC logotype

Annotation of /libgig/trunk/src/Serialization.h

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3776 - (hide annotations) (download) (as text)
Sat May 23 19:26:07 2020 UTC (4 years ago) by schoenebeck
File MIME type: text/x-c++hdr
File size: 69548 byte(s)
* Serialization.cpp/.h: Added built-in support for C++ Set<>
  objects (a.k.a. std::set from the STL).

* Bumped version (4.2.0.svn14).

1 schoenebeck 3138 /***************************************************************************
2     * *
3 schoenebeck 3771 * Copyright (C) 2017-2020 Christian Schoenebeck *
4 schoenebeck 3476 * <cuse@users.sourceforge.net> *
5 schoenebeck 3138 * *
6     * This library is part of libgig. *
7     * *
8     * This library is free software; you can redistribute it and/or modify *
9     * it under the terms of the GNU General Public License as published by *
10     * the Free Software Foundation; either version 2 of the License, or *
11     * (at your option) any later version. *
12     * *
13     * This library is distributed in the hope that it will be useful, *
14     * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16     * GNU General Public License for more details. *
17     * *
18     * You should have received a copy of the GNU General Public License *
19     * along with this library; if not, write to the Free Software *
20     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, *
21     * MA 02111-1307 USA *
22     ***************************************************************************/
23    
24     #ifndef LIBGIG_SERIALIZATION_H
25     #define LIBGIG_SERIALIZATION_H
26    
27     #ifdef HAVE_CONFIG_H
28     # include <config.h>
29     #endif
30    
31     #include <stdint.h>
32     #include <stdio.h>
33     #include <typeinfo>
34     #include <string>
35     #include <vector>
36     #include <map>
37 schoenebeck 3776 #include <set>
38 schoenebeck 3156 #include <time.h>
39 schoenebeck 3198 #include <stdarg.h>
40 schoenebeck 3773 #include <assert.h>
41 schoenebeck 3775 #include <functional>
42 schoenebeck 3178
43     #ifndef __has_extension
44     # define __has_extension(x) 0
45     #endif
46    
47     #ifndef HAS_BUILTIN_TYPE_TRAITS
48     # if __cplusplus >= 201103L
49     # define HAS_BUILTIN_TYPE_TRAITS 1
50     # elif ( __has_extension(is_class) && __has_extension(is_enum) )
51     # define HAS_BUILTIN_TYPE_TRAITS 1
52     # elif ( __GNUC__ > 4 || ( __GNUC__ == 4 && __GNUC_MINOR__ >= 3 ) )
53     # define HAS_BUILTIN_TYPE_TRAITS 1
54     # elif _MSC_VER >= 1400 /* MS Visual C++ 8.0 (Visual Studio 2005) */
55     # define HAS_BUILTIN_TYPE_TRAITS 1
56     # elif __INTEL_COMPILER >= 1100
57     # define HAS_BUILTIN_TYPE_TRAITS 1
58     # else
59     # define HAS_BUILTIN_TYPE_TRAITS 0
60     # endif
61     #endif
62    
63     #if !HAS_BUILTIN_TYPE_TRAITS
64 schoenebeck 3163 # include <tr1/type_traits>
65 schoenebeck 3167 # define LIBGIG_IS_CLASS(type) std::tr1::__is_union_or_class<type>::value //NOTE: without compiler support we cannot distinguish union from class
66     #else
67     # define LIBGIG_IS_CLASS(type) __is_class(type)
68 schoenebeck 3163 #endif
69 schoenebeck 3138
70     /** @brief Serialization / deserialization framework.
71     *
72     * See class Archive as starting point for how to implement serialization and
73     * deserialization with your application.
74     *
75     * The classes in this namespace allow to serialize and deserialize native
76     * C++ objects in a portable, easy and flexible way. Serialization is a
77     * technique that allows to transform the current state and data of native
78     * (in this case C++) objects into a data stream (including all other objects
79     * the "serialized" objects relate to); the data stream may then be sent over
80     * "wire" (for example via network connection to another computer, which might
81     * also have a different OS, CPU architecture, native memory word size and
82     * endian type); and finally the data stream would be "deserialized" on that
83     * receiver side, that is transformed again to modify all objects and data
84     * structures on receiver side to resemble the objects' state and data as it
85     * was originally on sender side.
86     *
87     * In contrast to many other already existing serialization frameworks, this
88     * implementation has a strong focus on robustness regarding long-term changes
89     * to the serialized C++ classes of the serialized objects. So even if sender
90     * and receiver are using different versions of their serialized/deserialized
91     * C++ classes, structures and data types (thus having different data structure
92     * layout to a certain extent), this framework aims trying to automatically
93     * adapt its serialization and deserialization process in that case so that
94     * the deserialized objects on receiver side would still reflect the overall
95     * expected states and overall data as intended by the sender. For being able to
96     * do so, this framework stores all kind of additional information about each
97     * serialized object and each data structure member (for example name of each
98     * data structure member, but also the offset of each member within its
99     * containing data structure, precise data types, and more).
100     *
101     * Like most other serialization frameworks, this frameworks does not require a
102     * tree-structured layout of the serialized data structures. So it automatically
103     * handles also cyclic dependencies between serialized data structures
104     * correctly, without i.e. causing endless recursion or redundancy.
105     *
106     * Additionally this framework also allows partial deserialization. Which means
107     * the receiver side may for example decide that it wants to restrict
108     * deserialization so that it would only modify certain objects or certain
109     * members by the deserialization process, leaving all other ones untouched.
110     * So this partial deserialization technique for example allows to implement
111     * flexible preset features for applications in a powerful and easy way.
112     */
113     namespace Serialization {
114    
115 schoenebeck 3146 // just symbol prototyping
116     class DataType;
117     class Object;
118     class Member;
119 schoenebeck 3138 class Archive;
120 schoenebeck 3146 class ObjectPool;
121 schoenebeck 3138 class Exception;
122    
123 schoenebeck 3775 /** @brief Textual string.
124     *
125     * This type is used for built-in automatic serialization / deserialization
126     * of C++ @c String objects (a.k.a. @c std::string from the STL). This
127     * framework supports serializing this common data type out of the box and
128     * is handled by this framework as it was a primitive C++ data type.
129     */
130 schoenebeck 3138 typedef std::string String;
131    
132 schoenebeck 3775 /** @brief Array<> template.
133     *
134     * This type is used for built-in automatic serialization / deserialization
135     * of C++ array containers (a.k.a. @c std::vector from the STL). This
136     * framework supports serializing this common data type out of the box, with
137     * only one constraint: the precise element type used with arrays must be
138     * serializable. So the array's element type should either be a) any
139     * primitive data type (e.g. @c int, @c double, etc.) or b) any other data
140     * structure or class types enjoying out of the box serialization support by
141     * this framework, or c) if it is a custom @c struct or @c class then it
142     * must have a @c serialize() method implementation.
143     */
144     template<class T>
145     using Array = std::vector<T>;
146    
147 schoenebeck 3776 /** @brief Set<> template.
148     *
149     * This type is used for built-in automatic serialization / deserialization
150     * of C++ unique data set containers (a.k.a. @c std::set from the STL). This
151     * framework supports serializing this common data type out of the box, with
152     * the following constraint: the precise key type used with sets must be
153     * either a primitive data type (e.g. @c int, @c double, @c bool, etc.) or a
154     * @c String object.
155     */
156     template<class T>
157     using Set = std::set<T>;
158    
159 schoenebeck 3183 /** @brief Raw data stream of serialized C++ objects.
160     *
161     * This data type is used for the data stream as a result of serializing
162     * your C++ objects with Archive::serialize(), and for native raw data
163     * representation of individual serialized C/C++ objects, members and variables.
164     *
165     * @see Archive::rawData(), Object::rawData()
166     */
167 schoenebeck 3138 typedef std::vector<uint8_t> RawData;
168    
169 schoenebeck 3183 /** @brief Abstract identifier for serialized C++ objects.
170     *
171     * This data type is used for identifying serialized C++ objects and members
172     * of your C++ objects. It is important to know that such an ID might not
173     * necessarily be unique. For example the ID of one C++ object might often
174     * be identical to the ID of the first member of that particular C++ object.
175     * That's why there is additionally the concept of an UID in this framework.
176     *
177     * @see UID
178     */
179 schoenebeck 3138 typedef void* ID;
180    
181 schoenebeck 3183 /** @brief Version number data type.
182     *
183     * This data type is used for maintaining version number information of
184     * your C++ class implementations.
185     *
186     * @see Archive::setVersion() and Archive::setMinVersion()
187     */
188 schoenebeck 3138 typedef uint32_t Version;
189    
190 schoenebeck 3183 /** @brief To which time zone a certain timing information relates to.
191     *
192     * The constants in this enum type are used to define to which precise time
193     * zone a time stamp relates to.
194     */
195 schoenebeck 3156 enum time_base_t {
196 schoenebeck 3183 LOCAL_TIME, ///< The time stamp relates to the machine's local time zone. Request a time stamp in local time if you want to present that time stamp to the end user.
197     UTC_TIME ///< The time stamp relates to "Greenwhich Mean Time" zone, also known as "Coordinated Universal Time". Request time stamp with UTC if you want to compare that time stamp with other time stamps.
198 schoenebeck 3156 };
199    
200 schoenebeck 3183 /** @brief Check whether data is a C/C++ @c enum type.
201     *
202     * Returns true if the supplied C++ variable or object is of a C/C++ @c enum
203     * type.
204     *
205     * @param data - the variable or object whose data type shall be checked
206     */
207 schoenebeck 3138 template<typename T>
208     bool IsEnum(const T& data) {
209 schoenebeck 3178 #if !HAS_BUILTIN_TYPE_TRAITS
210 schoenebeck 3165 return std::tr1::is_enum<T>::value;
211 schoenebeck 3164 #else
212 schoenebeck 3138 return __is_enum(T);
213 schoenebeck 3164 #endif
214 schoenebeck 3138 }
215    
216 schoenebeck 3183 /** @brief Check whether data is a C++ @c union type.
217     *
218     * Returns true if the supplied C++ variable or object is of a C/C++ @c union
219     * type. Note that the result of this function is only reliable if the C++
220     * compiler you are using has support for built-in type traits. If your C++
221     * compiler does not have built-in type traits support, then this function
222     * will simply return @c false on all your calls.
223     *
224     * @param data - the variable or object whose data type shall be checked
225     */
226 schoenebeck 3138 template<typename T>
227     bool IsUnion(const T& data) {
228 schoenebeck 3178 #if !HAS_BUILTIN_TYPE_TRAITS
229 schoenebeck 3166 return false; // without compiler support we cannot distinguish union from class
230 schoenebeck 3164 #else
231 schoenebeck 3138 return __is_union(T);
232 schoenebeck 3164 #endif
233 schoenebeck 3138 }
234    
235 schoenebeck 3183 /** @brief Check whether data is a C/C++ @c struct or C++ @c class type.
236     *
237     * Returns true if the supplied C++ variable or object is of C/C++ @c struct
238     * or C++ @c class type. Note that if you are using a C++ compiler which
239     * does have built-in type traits support, then this function will also
240     * return @c true on C/C++ @c union types.
241     *
242     * @param data - the variable or object whose data type shall be checked
243     */
244 schoenebeck 3138 template<typename T>
245     bool IsClass(const T& data) {
246 schoenebeck 3178 #if !HAS_BUILTIN_TYPE_TRAITS
247 schoenebeck 3166 return std::tr1::__is_union_or_class<T>::value; // without compiler support we cannot distinguish union from class
248 schoenebeck 3164 #else
249 schoenebeck 3138 return __is_class(T);
250 schoenebeck 3164 #endif
251 schoenebeck 3138 }
252    
253     /*template<typename T>
254     bool IsTrivial(T data) {
255     return __is_trivial(T);
256     }*/
257    
258     /*template<typename T>
259     bool IsPOD(T data) {
260     return __is_pod(T);
261     }*/
262    
263 schoenebeck 3775 /*template<typename T>
264     bool IsArray(const T& data) {
265     return false;
266     }*/
267    
268     /*template<typename T>
269     bool IsArray(const Array<T>& data) {
270     return true;
271     }*/
272    
273 schoenebeck 3776 template<typename T> inline
274     String toString(const T& value) {
275     return std::to_string(value);
276     }
277    
278     template<> inline
279     String toString(const String& value) {
280     return value;
281     }
282    
283 schoenebeck 3185 /** @brief Unique identifier referring to one specific native C++ object, member, fundamental variable, or any other native C++ data.
284 schoenebeck 3138 *
285 schoenebeck 3185 * Reflects a unique identifier for one specific serialized C++ data, i.e.
286     * C++ class instance, C/C++ struct instance, member, primitive pointer,
287     * fundamental variables, or any other native C/C++ data originally being
288     * serialized.
289 schoenebeck 3183 *
290     * A unique identifier is composed of an id (an identifier which is not
291     * necessarily unique) and a size. Since the underlying ID is derived from
292     * the original C++ object's memory location, such an ID is not sufficient
293     * to distinguish a particular C++ object from the first member of that C++
294     * object, since both typically share the same memory address. So
295     * additionally the memory size of the respective object or member is
296     * bundled with UID objects to make them unique and distinguishable.
297 schoenebeck 3138 */
298     class UID {
299     public:
300 schoenebeck 3183 ID id; ///< Abstract non-unique ID of the object or member in question.
301     size_t size; ///< Memory size of the object or member in question.
302 schoenebeck 3138
303     bool isValid() const;
304 schoenebeck 3183 operator bool() const { return isValid(); } ///< Same as calling isValid().
305 schoenebeck 3138 //bool operator()() const { return isValid(); }
306     bool operator==(const UID& other) const { return id == other.id && size == other.size; }
307     bool operator!=(const UID& other) const { return id != other.id || size != other.size; }
308     bool operator<(const UID& other) const { return id < other.id || (id == other.id && size < other.size); }
309     bool operator>(const UID& other) const { return id > other.id || (id == other.id && size > other.size); }
310    
311 schoenebeck 3183 /** @brief Create an unique indentifier for a native C++ object/member/variable.
312     *
313     * Creates and returns an unique identifier for the passed native C++
314     * object, object member or variable. For the same C++ object/member/variable
315     * this function will always return the same UID. For all other ones,
316     * this function is guaranteed to return a different UID.
317     */
318 schoenebeck 3138 template<typename T>
319     static UID from(const T& obj) {
320     return Resolver<T>::resolve(obj);
321     }
322    
323     protected:
324     // UID resolver for non-pointer types
325     template<typename T>
326     struct Resolver {
327     static UID resolve(const T& obj) {
328 schoenebeck 3168 const UID uid = { (ID) &obj, sizeof(obj) };
329     return uid;
330 schoenebeck 3138 }
331     };
332    
333     // UID resolver for pointer types (of 1st degree)
334     template<typename T>
335     struct Resolver<T*> {
336     static UID resolve(const T* const & obj) {
337 schoenebeck 3168 const UID uid = { (ID) obj, sizeof(*obj) };
338     return uid;
339 schoenebeck 3138 }
340     };
341     };
342    
343     /**
344     * Reflects an invalid UID and behaves similar to NULL as invalid value for
345 schoenebeck 3183 * pointer types. All UID objects are first initialized with this value,
346     * and it essentially an all zero object.
347 schoenebeck 3138 */
348     extern const UID NO_UID;
349    
350 schoenebeck 3183 /** @brief Chain of UIDs.
351     *
352     * This data type is used for native C++ pointers. The first member of the
353     * UID chain is the unique identifier of the C++ pointer itself, then the
354     * following UIDs are the respective objects or variables the pointer is
355     * pointing to. The size (the amount of elements) of the UIDChain depends
356     * solely on the degree of the pointer type. For example the following C/C++
357     * pointer:
358     * @code
359     * int* pNumber;
360     * @endcode
361     * is an integer pointer of first degree. Such a pointer would have a
362     * UIDChain with 2 members: the first element would be the UID of the
363     * pointer itself, the second element of the chain would be the integer data
364     * that pointer is pointing to. In the following example:
365     * @code
366     * bool*** pppSomeFlag;
367     * @endcode
368     * That boolean pointer would be of third degree, and thus its UIDChain
369     * would have a size of 4 (elements).
370     *
371     * Accordingly a non pointer type like:
372     * @code
373     * float f;
374     * @endcode
375     * would yield in a UIDChain of size 1.
376     *
377     * Since however this serialization framework currently only supports
378     * pointers of first degree yet, all UIDChains are currently either of
379     * size 1 or 2, which might change in future though.
380     */
381 schoenebeck 3138 typedef std::vector<UID> UIDChain;
382    
383 schoenebeck 3392 #if LIBGIG_SERIALIZATION_INTERNAL
384 schoenebeck 3146 // prototyping of private internal friend functions
385 schoenebeck 3150 static String _encodePrimitiveValue(const Object& obj);
386 schoenebeck 3146 static DataType _popDataTypeBlob(const char*& p, const char* end);
387     static Member _popMemberBlob(const char*& p, const char* end);
388     static Object _popObjectBlob(const char*& p, const char* end);
389     static void _popPrimitiveValue(const char*& p, const char* end, Object& obj);
390 schoenebeck 3150 static String _primitiveObjectValueToString(const Object& obj);
391 schoenebeck 3169 // |
392     template<typename T>
393     static T _primitiveObjectValueToNumber(const Object& obj);
394 schoenebeck 3392 #endif // LIBGIG_SERIALIZATION_INTERNAL
395 schoenebeck 3146
396 schoenebeck 3138 /** @brief Abstract reflection of a native C++ data type.
397     *
398 schoenebeck 3183 * Provides detailed information about a serialized C++ data type, whether
399     * it is a fundamental C/C++ data type (like @c int, @c float, @c char,
400     * etc.) or custom defined data types like a C++ @c class, C/C++ @c struct,
401     * @c enum, as well as other features of the respective data type like its
402     * native memory size and more.
403     *
404     * All informations provided by this class are retrieved from the
405     * respective individual C++ objects, their members and other data when
406     * they are serialized, and all those information are stored with the
407     * serialized archive and its resulting data stream. Due to the availability
408     * of these extensive data type information within serialized archives, this
409     * framework is capable to use them in order to adapt its deserialization
410     * process upon subsequent changes to your individual C++ classes.
411 schoenebeck 3138 */
412     class DataType {
413     public:
414     DataType();
415 schoenebeck 3183 size_t size() const { return m_size; } ///< Returns native memory size of the respective C++ object or variable.
416 schoenebeck 3138 bool isValid() const;
417     bool isPointer() const;
418     bool isClass() const;
419     bool isPrimitive() const;
420 schoenebeck 3771 bool isString() const;
421 schoenebeck 3138 bool isInteger() const;
422     bool isReal() const;
423     bool isBool() const;
424     bool isEnum() const;
425 schoenebeck 3775 bool isArray() const;
426 schoenebeck 3776 bool isSet() const;
427 schoenebeck 3138 bool isSigned() const;
428 schoenebeck 3183 operator bool() const { return isValid(); } ///< Same as calling isValid().
429 schoenebeck 3138 //bool operator()() const { return isValid(); }
430     bool operator==(const DataType& other) const;
431     bool operator!=(const DataType& other) const;
432     bool operator<(const DataType& other) const;
433     bool operator>(const DataType& other) const;
434     String asLongDescr() const;
435 schoenebeck 3183 String baseTypeName() const;
436 schoenebeck 3173 String customTypeName(bool demangle = false) const;
437 schoenebeck 3138
438 schoenebeck 3183 /** @brief Construct a DataType object for the given native C++ data.
439     *
440     * Use this function to create corresponding DataType objects for
441     * native C/C++ objects, members and variables.
442     *
443     * @param data - native C/C++ object/member/variable a DataType object
444     * shall be created for
445     * @returns corresponding DataType object for the supplied native C/C++
446     * object/member/variable
447     */
448 schoenebeck 3138 template<typename T>
449     static DataType dataTypeOf(const T& data) {
450     return Resolver<T>::resolve(data);
451     }
452    
453     protected:
454     DataType(bool isPointer, int size, String baseType, String customType = "");
455    
456     template<typename T, bool T_isPointer>
457     struct ResolverBase {
458     static DataType resolve(const T& data) {
459     const std::type_info& type = typeid(data);
460     const int sz = sizeof(data);
461    
462     // for primitive types we are using our own type names instead of
463     // using std:::type_info::name(), because the precise output of the
464     // latter may vary between compilers
465     if (type == typeid(int8_t)) return DataType(T_isPointer, sz, "int8");
466     if (type == typeid(uint8_t)) return DataType(T_isPointer, sz, "uint8");
467     if (type == typeid(int16_t)) return DataType(T_isPointer, sz, "int16");
468     if (type == typeid(uint16_t)) return DataType(T_isPointer, sz, "uint16");
469     if (type == typeid(int32_t)) return DataType(T_isPointer, sz, "int32");
470     if (type == typeid(uint32_t)) return DataType(T_isPointer, sz, "uint32");
471     if (type == typeid(int64_t)) return DataType(T_isPointer, sz, "int64");
472     if (type == typeid(uint64_t)) return DataType(T_isPointer, sz, "uint64");
473 schoenebeck 3773 if (type == typeid(size_t)) {
474     if (sz == 1) return DataType(T_isPointer, sz, "uint8");
475     if (sz == 2) return DataType(T_isPointer, sz, "uint16");
476     if (sz == 4) return DataType(T_isPointer, sz, "uint32");
477     if (sz == 8) return DataType(T_isPointer, sz, "uint64");
478     else assert(false /* unknown size_t size */);
479     }
480     if (type == typeid(ssize_t)) {
481     if (sz == 1) return DataType(T_isPointer, sz, "int8");
482     if (sz == 2) return DataType(T_isPointer, sz, "int16");
483     if (sz == 4) return DataType(T_isPointer, sz, "int32");
484     if (sz == 8) return DataType(T_isPointer, sz, "int64");
485     else assert(false /* unknown ssize_t size */);
486     }
487 schoenebeck 3138 if (type == typeid(bool)) return DataType(T_isPointer, sz, "bool");
488     if (type == typeid(float)) return DataType(T_isPointer, sz, "real32");
489     if (type == typeid(double)) return DataType(T_isPointer, sz, "real64");
490 schoenebeck 3771 if (type == typeid(String)) return DataType(T_isPointer, sz, "String");
491 schoenebeck 3138
492     if (IsEnum(data)) return DataType(T_isPointer, sz, "enum", rawCppTypeNameOf(data));
493     if (IsUnion(data)) return DataType(T_isPointer, sz, "union", rawCppTypeNameOf(data));
494     if (IsClass(data)) return DataType(T_isPointer, sz, "class", rawCppTypeNameOf(data));
495    
496     return DataType();
497     }
498     };
499    
500     // DataType resolver for non-pointer types
501     template<typename T>
502     struct Resolver : ResolverBase<T,false> {
503     static DataType resolve(const T& data) {
504     return ResolverBase<T,false>::resolve(data);
505     }
506     };
507    
508     // DataType resolver for pointer types (of 1st degree)
509     template<typename T>
510     struct Resolver<T*> : ResolverBase<T,true> {
511     static DataType resolve(const T*& data) {
512     return ResolverBase<T,true>::resolve(*data);
513     }
514     };
515    
516 schoenebeck 3775 // DataType resolver for non-pointer Array<> container object types.
517 schoenebeck 3138 template<typename T>
518 schoenebeck 3775 struct Resolver<Array<T>> {
519     static DataType resolve(const Array<T>& data) {
520     const int sz = sizeof(data);
521     T unused;
522     return DataType(false, sz, "Array", rawCppTypeNameOf(unused));
523     }
524     };
525    
526     // DataType resolver for Array<> pointer types (of 1st degree).
527     template<typename T>
528     struct Resolver<Array<T>*> {
529     static DataType resolve(const Array<T>*& data) {
530     const int sz = sizeof(*data);
531     T unused;
532     return DataType(true, sz, "Array", rawCppTypeNameOf(unused));
533     }
534     };
535    
536 schoenebeck 3776 // DataType resolver for non-pointer Set<> container object types.
537 schoenebeck 3775 template<typename T>
538 schoenebeck 3776 struct Resolver<Set<T>> {
539     static DataType resolve(const Set<T>& data) {
540     const int sz = sizeof(data);
541     T unused;
542     return DataType(false, sz, "Set", rawCppTypeNameOf(unused));
543     }
544     };
545    
546     // DataType resolver for Set<> pointer types (of 1st degree).
547     template<typename T>
548     struct Resolver<Set<T>*> {
549     static DataType resolve(const Set<T>*& data) {
550     const int sz = sizeof(*data);
551     T unused;
552     return DataType(true, sz, "Set", rawCppTypeNameOf(unused));
553     }
554     };
555    
556     template<typename T>
557 schoenebeck 3138 static String rawCppTypeNameOf(const T& data) {
558     #if defined _MSC_VER // Microsoft compiler ...
559 schoenebeck 3476 String name = typeid(data).raw_name();
560 schoenebeck 3138 #else // i.e. especially GCC and clang ...
561     String name = typeid(data).name();
562     #endif
563     //while (!name.empty() && name[0] >= 0 && name[0] <= 9)
564     // name = name.substr(1);
565     return name;
566     }
567    
568     private:
569     String m_baseTypeName;
570     String m_customTypeName;
571     int m_size;
572     bool m_isPointer;
573    
574 schoenebeck 3392 #if LIBGIG_SERIALIZATION_INTERNAL
575 schoenebeck 3138 friend DataType _popDataTypeBlob(const char*& p, const char* end);
576 schoenebeck 3392 #endif
577 schoenebeck 3150 friend class Archive;
578 schoenebeck 3138 };
579    
580     /** @brief Abstract reflection of a native C++ class/struct's member variable.
581     *
582     * Provides detailed information about a specific C++ member variable of
583     * serialized C++ object, like its C++ data type, offset of this member
584     * within its containing data structure/class, its C++ member variable name
585     * and more.
586 schoenebeck 3183 *
587     * Consider you defined the following user defined C/C++ @c struct type in
588     * your application:
589     * @code
590     * struct Foo {
591     * int a;
592     * bool b;
593     * double someValue;
594     * };
595     * @endcode
596     * Then @c a, @c b and @c someValue are "members" of @c struct @c Foo for
597     * instance. So that @c struct would have 3 members in the latter example.
598     *
599     * @see Object::members()
600 schoenebeck 3138 */
601     class Member {
602     public:
603     Member();
604 schoenebeck 3183 UID uid() const;
605     String name() const;
606 schoenebeck 3775 ssize_t offset() const;
607 schoenebeck 3183 const DataType& type() const;
608 schoenebeck 3138 bool isValid() const;
609 schoenebeck 3183 operator bool() const { return isValid(); } ///< Same as calling isValid().
610 schoenebeck 3138 //bool operator()() const { return isValid(); }
611     bool operator==(const Member& other) const;
612     bool operator!=(const Member& other) const;
613     bool operator<(const Member& other) const;
614     bool operator>(const Member& other) const;
615    
616     protected:
617 schoenebeck 3775 Member(String name, UID uid, ssize_t offset, DataType type);
618 schoenebeck 3138 friend class Archive;
619    
620     private:
621     UID m_uid;
622 schoenebeck 3775 ssize_t m_offset;
623 schoenebeck 3138 String m_name;
624     DataType m_type;
625    
626 schoenebeck 3392 #if LIBGIG_SERIALIZATION_INTERNAL
627 schoenebeck 3138 friend Member _popMemberBlob(const char*& p, const char* end);
628 schoenebeck 3392 #endif
629 schoenebeck 3138 };
630    
631 schoenebeck 3185 /** @brief Abstract reflection of some native serialized C/C++ data.
632 schoenebeck 3138 *
633 schoenebeck 3185 * When your native C++ objects are serialized, all native data is
634     * translated and reflected by such an Object reflection. So each instance
635     * of your serialized native C++ class objects become available as an
636     * Object, but also each member variable of your C++ objects is translated
637     * into an Object, and any other native C/C++ data. So essentially every
638     * native data is turned into its own Object and accessible by this API.
639 schoenebeck 3183 *
640 schoenebeck 3185 * For each one of those Object reflections, this class provides detailed
641     * information about their native origin. For example if an Object
642     * represents a native C++ class instante, then it provides access to its
643     * C++ class/struct name, to its C++ member variables, its native memory
644     * size and much more.
645     *
646 schoenebeck 3183 * Even though this framework allows you to adjust abstract Object instances
647     * to a certain extent, most of the methods of this Object class are
648     * read-only though and the actual modifyable methods are made available
649     * not as part of this Object class, but as part of the Archive class
650 schoenebeck 3185 * instead. This design decision was made for performance and safety
651     * reasons.
652 schoenebeck 3183 *
653     * @see Archive::setIntValue() as an example for modifying Object instances.
654 schoenebeck 3138 */
655     class Object {
656     public:
657     Object();
658     Object(UIDChain uidChain, DataType type);
659    
660 schoenebeck 3183 UID uid(int index = 0) const;
661     const UIDChain& uidChain() const;
662     const DataType& type() const;
663     const RawData& rawData() const;
664     Version version() const;
665     Version minVersion() const;
666 schoenebeck 3138 bool isVersionCompatibleTo(const Object& other) const;
667 schoenebeck 3183 std::vector<Member>& members();
668     const std::vector<Member>& members() const;
669 schoenebeck 3138 Member memberNamed(String name) const;
670 schoenebeck 3153 Member memberByUID(const UID& uid) const;
671 schoenebeck 3138 std::vector<Member> membersOfType(const DataType& type) const;
672     int sequenceIndexOf(const Member& member) const;
673     bool isValid() const;
674 schoenebeck 3183 operator bool() const { return isValid(); } ///< Same as calling isValid().
675 schoenebeck 3138 //bool operator()() const { return isValid(); }
676     bool operator==(const Object& other) const;
677     bool operator!=(const Object& other) const;
678     bool operator<(const Object& other) const;
679     bool operator>(const Object& other) const;
680 schoenebeck 3776 void setNativeValueFromString(const String& s);
681 schoenebeck 3138
682 schoenebeck 3153 protected:
683     void remove(const Member& member);
684 schoenebeck 3182 void setVersion(Version v);
685     void setMinVersion(Version v);
686 schoenebeck 3153
687 schoenebeck 3138 private:
688     DataType m_type;
689     UIDChain m_uid;
690     Version m_version;
691     Version m_minVersion;
692     RawData m_data;
693     std::vector<Member> m_members;
694 schoenebeck 3775 std::function<void(Object& dstObj, const Object& srcObj, void* syncer)> m_sync;
695 schoenebeck 3138
696 schoenebeck 3392 #if LIBGIG_SERIALIZATION_INTERNAL
697 schoenebeck 3150 friend String _encodePrimitiveValue(const Object& obj);
698 schoenebeck 3138 friend Object _popObjectBlob(const char*& p, const char* end);
699     friend void _popPrimitiveValue(const char*& p, const char* end, Object& obj);
700 schoenebeck 3150 friend String _primitiveObjectValueToString(const Object& obj);
701 schoenebeck 3392 // |
702 schoenebeck 3169 template<typename T>
703     friend T _primitiveObjectValueToNumber(const Object& obj);
704 schoenebeck 3392 #endif // LIBGIG_SERIALIZATION_INTERNAL
705 schoenebeck 3169
706 schoenebeck 3150 friend class Archive;
707 schoenebeck 3138 };
708    
709     /** @brief Destination container for serialization, and source container for deserialization.
710     *
711     * This is the main class for implementing serialization and deserialization
712     * with your C++ application. This framework does not require a a tree
713     * structured layout of your C++ objects being serialized/deserialized, it
714     * uses a concept of a "root" object though. So to start serialization
715     * construct an empty Archive object and then instruct it to serialize your
716     * C++ objects by pointing it to your "root" object:
717     * @code
718     * Archive a;
719     * a.serialize(&myRootObject);
720 schoenebeck 3142 * @endcode
721 schoenebeck 3138 * Or if you prefer the look of operator based code:
722     * @code
723     * Archive a;
724     * a << myRootObject;
725 schoenebeck 3142 * @endcode
726 schoenebeck 3138 * The Archive object will then serialize all members of the passed C++
727     * object, and will recursively serialize all other C++ objects which it
728     * contains or points to. So the root object is the starting point for the
729     * overall serialization. After the serialize() method returned, you can
730     * then access the serialized data stream by calling rawData() and send that
731     * data stream over "wire", or store it on disk or whatever you may intend
732     * to do with it.
733     *
734     * Then on receiver side likewise, you create a new Archive object, pass the
735     * received data stream i.e. via constructor to the Archive object and call
736     * deserialize() by pointing it to the root object on receiver side:
737     * @code
738     * Archive a(rawDataStream);
739     * a.deserialize(&myRootObject);
740 schoenebeck 3142 * @endcode
741 schoenebeck 3138 * Or with operator instead:
742     * @code
743     * Archive a(rawDataStream);
744     * a >> myRootObject;
745 schoenebeck 3142 * @endcode
746 schoenebeck 3138 * Now this framework automatically handles serialization and
747     * deserialization of fundamental data types automatically for you (like
748     * i.e. char, int, long int, float, double, etc.). However for your own
749     * custom C++ classes and structs you must implement one method which
750     * defines which members of your class should actually be serialized and
751     * deserialized. That method to be added must have the following signature:
752     * @code
753     * void serialize(Serialization::Archive* archive);
754     * @endcode
755     * So let's say you have the following simple data structures:
756     * @code
757     * struct Foo {
758     * int a;
759     * bool b;
760     * double c;
761     * };
762     *
763     * struct Bar {
764     * char one;
765     * float two;
766     * Foo foo1;
767     * Foo* pFoo2;
768     * Foo* pFoo3DontTouchMe; // shall not be serialized/deserialized
769     * };
770     * @endcode
771     * So in order to be able to serialize and deserialize objects of those two
772     * structures you would first add the mentioned method to each struct
773     * definition (i.e. in your header file):
774     * @code
775     * struct Foo {
776     * int a;
777     * bool b;
778     * double c;
779     *
780     * void serialize(Serialization::Archive* archive);
781     * };
782     *
783     * struct Bar {
784     * char one;
785     * float two;
786     * Foo foo1;
787     * Foo* pFoo2;
788     * Foo* pFoo3DontTouchMe; // shall not be serialized/deserialized
789     *
790     * void serialize(Serialization::Archive* archive);
791     * };
792     * @endcode
793     * And then you would implement those two new methods like this (i.e. in
794     * your .cpp file):
795     * @code
796     * #define SRLZ(member) \
797     * archive->serializeMember(*this, member, #member);
798     *
799     * void Foo::serialize(Serialization::Archive* archive) {
800     * SRLZ(a);
801     * SRLZ(b);
802     * SRLZ(c);
803     * }
804     *
805     * void Bar::serialize(Serialization::Archive* archive) {
806     * SRLZ(one);
807     * SRLZ(two);
808     * SRLZ(foo1);
809     * SRLZ(pFoo2);
810     * // leaving out pFoo3DontTouchMe here
811     * }
812     * @endcode
813     * Now when you serialize such a Bar object, this framework will also
814     * automatically serialize the respective Foo object(s) accordingly, also
815     * for the pFoo2 pointer for instance (as long as it is not a NULL pointer
816     * that is).
817     *
818     * Note that there is only one method that you need to implement. So the
819     * respective serialize() method implementation of your classes/structs are
820     * both called for serialization, as well as for deserialization!
821 schoenebeck 3182 *
822     * In case you need to enforce backward incompatiblity for one of your C++
823     * classes, you can do so by setting a version and minimum version for your
824     * class (see @c setVersion() and @c setMinVersion() for details).
825 schoenebeck 3138 */
826     class Archive {
827     public:
828 schoenebeck 3774 /** @brief Current activity of @c Archive object.
829     */
830     enum operation_t {
831     OPERATION_NONE, ///< Archive is currently neither serializing, nor deserializing.
832     OPERATION_SERIALIZE, ///< Archive is currently serializing.
833     OPERATION_DESERIALIZE ///< Archive is currently deserializing.
834     };
835    
836 schoenebeck 3138 Archive();
837     Archive(const RawData& data);
838     Archive(const uint8_t* data, size_t size);
839     virtual ~Archive();
840    
841 schoenebeck 3183 /** @brief Initiate serialization.
842     *
843     * Initiates serialization of all native C++ objects, which means
844     * capturing and storing the current data of all your C++ objects as
845     * content of this Archive.
846     *
847     * This framework has a concept of a "root" object which you must pass
848     * to this method. The root object is the starting point for
849     * serialization of your C++ objects. The framework will then
850     * recursively serialize all members of that C++ object an continue to
851     * serialize all other C++ objects that it might contain or point to.
852     *
853     * After this method returned, you might traverse all serialized objects
854     * by walking them starting from the rootObject(). You might then modify
855     * that abstract reflection of your C++ objects and finally you might
856     * call rawData() to get an encoded raw data stream which you might use
857     * for sending it "over wire" to somewhere where it is going to be
858     * deserialized later on.
859     *
860     * Note that whenever you call this method, the previous content of this
861     * Archive will first be cleared.
862     *
863     * @param obj - native C++ root object where serialization shall start
864     * @see Archive::operator<<()
865     */
866 schoenebeck 3138 template<typename T>
867     void serialize(const T* obj) {
868     m_operation = OPERATION_SERIALIZE;
869     m_allObjects.clear();
870     m_rawData.clear();
871     m_root = UID::from(obj);
872     const_cast<T*>(obj)->serialize(this);
873     encode();
874     m_operation = OPERATION_NONE;
875     }
876    
877 schoenebeck 3183 /** @brief Initiate deserialization.
878     *
879     * Initiates deserialization of all native C++ objects, which means all
880     * your C++ objects will be restored with the values contained in this
881     * Archive. So that also means calling deserialize() only makes sense if
882     * this a non-empty Archive, which i.e. is the case if you either called
883     * serialize() with this Archive object before or if you passed a
884     * previously serialized raw data stream to the constructor of this
885     * Archive object.
886     *
887     * This framework has a concept of a "root" object which you must pass
888     * to this method. The root object is the starting point for
889     * deserialization of your C++ objects. The framework will then
890     * recursively deserialize all members of that C++ object an continue to
891     * deserialize all other C++ objects that it might contain or point to,
892     * according to the values stored in this Archive.
893     *
894     * @param obj - native C++ root object where deserialization shall start
895     * @see Archive::operator>>()
896     *
897     * @throws Exception if the data stored in this Archive cannot be
898     * restored to the C++ objects passed to this method, i.e.
899     * because of version or type incompatibilities.
900     */
901 schoenebeck 3138 template<typename T>
902     void deserialize(T* obj) {
903     Archive a;
904 schoenebeck 3774 a.m_operation = m_operation = OPERATION_DESERIALIZE;
905 schoenebeck 3138 obj->serialize(&a);
906     a.m_root = UID::from(obj);
907     Syncer s(a, *this);
908 schoenebeck 3774 a.m_operation = m_operation = OPERATION_NONE;
909 schoenebeck 3138 }
910    
911 schoenebeck 3183 /** @brief Initiate serialization of your C++ objects.
912     *
913     * Same as calling @c serialize(), this is just meant if you prefer
914     * to use operator based code instead, which you might find to be more
915     * intuitive.
916     *
917     * Example:
918     * @code
919     * Archive a;
920     * a << myRootObject;
921     * @endcode
922     *
923     * @see Archive::serialize() for more details.
924     */
925 schoenebeck 3138 template<typename T>
926     void operator<<(const T& obj) {
927     serialize(&obj);
928     }
929    
930 schoenebeck 3183 /** @brief Initiate deserialization of your C++ objects.
931     *
932     * Same as calling @c deserialize(), this is just meant if you prefer
933     * to use operator based code instead, which you might find to be more
934     * intuitive.
935     *
936     * Example:
937     * @code
938     * Archive a(rawDataStream);
939     * a >> myRootObject;
940     * @endcode
941     *
942     * @throws Exception if the data stored in this Archive cannot be
943     * restored to the C++ objects passed to this method, i.e.
944     * because of version or type incompatibilities.
945     *
946     * @see Archive::deserialize() for more details.
947     */
948 schoenebeck 3138 template<typename T>
949     void operator>>(T& obj) {
950     deserialize(&obj);
951     }
952    
953 schoenebeck 3150 const RawData& rawData();
954 schoenebeck 3138 virtual String rawDataFormat() const;
955    
956 schoenebeck 3183 /** @brief Serialize a native C/C++ member variable.
957     *
958     * This method is usually called by the serialize() method
959     * implementation of your C/C++ structs and classes, for each of the
960     * member variables that shall be serialized and deserialized
961     * automatically with this framework. It is recommend that you are not
962     * using this method name directly, but rather define a short hand C
963     * macro in your .cpp file like:
964     * @code
965     * #define SRLZ(member) \
966     * archive->serializeMember(*this, member, #member);
967     *
968 schoenebeck 3775 * struct Foo {
969     * int a;
970     * Bar b; // a custom struct or class having a serialize() method
971     * std::string c;
972     * std::vector<double> d;
973     *
974     * void serialize(Serialization::Archive* archive);
975     * };
976     *
977 schoenebeck 3183 * void Foo::serialize(Serialization::Archive* archive) {
978     * SRLZ(a);
979     * SRLZ(b);
980     * SRLZ(c);
981 schoenebeck 3775 * SRLZ(d);
982 schoenebeck 3183 * }
983     * @endcode
984 schoenebeck 3775 * As you can see, using such a macro makes your code more readable,
985     * compact and less error prone.
986 schoenebeck 3183 *
987     * It is completely up to you to decide which ones of your member
988     * variables shall automatically be serialized and deserialized with
989     * this framework. Only those member variables which are registered by
990     * calling this method will be serialized and deserialized. It does not
991     * really matter in which order you register your individiual member
992     * variables by calling this method, but the sequence is actually stored
993     * as meta information with the resulting archive and the resulting raw
994     * data stream. That meta information might then be used by this
995     * framework to automatically correct and adapt deserializing that
996     * archive later on for a future (or older) and potentially heavily
997     * modified version of your software. So it is recommended, even though
998     * also not required, that you may retain the sequence of your
999     * serializeMember() calls for your individual C++ classes' members over
1000     * all your software versions, to retain backward compatibility of older
1001     * archives as much as possible.
1002     *
1003     * @param nativeObject - native C++ object to be registered for
1004     * serialization / deserialization
1005     * @param nativeMember - native C++ member variable of @a nativeObject
1006     * to be registered for serialization /
1007     * deserialization
1008     * @param memberName - name of @a nativeMember to be stored with this
1009     * archive
1010 schoenebeck 3775 * @see serializeHeapMember() for variables on the RAM heap
1011 schoenebeck 3183 */
1012 schoenebeck 3138 template<typename T_classType, typename T_memberType>
1013     void serializeMember(const T_classType& nativeObject, const T_memberType& nativeMember, const char* memberName) {
1014 schoenebeck 3775 const ssize_t offset =
1015 schoenebeck 3182 ((const uint8_t*)(const void*)&nativeMember) -
1016     ((const uint8_t*)(const void*)&nativeObject);
1017 schoenebeck 3138 const UIDChain uids = UIDChainResolver<T_memberType>(nativeMember);
1018     const DataType type = DataType::dataTypeOf(nativeMember);
1019     const Member member(memberName, uids[0], offset, type);
1020     const UID parentUID = UID::from(nativeObject);
1021     Object& parent = m_allObjects[parentUID];
1022     if (!parent) {
1023     const UIDChain uids = UIDChainResolver<T_classType>(nativeObject);
1024     const DataType type = DataType::dataTypeOf(nativeObject);
1025     parent = Object(uids, type);
1026     }
1027     parent.members().push_back(member);
1028     const Object obj(uids, type);
1029     const bool bExistsAlready = m_allObjects.count(uids[0]);
1030     const bool isValidObject = obj;
1031     const bool bExistingObjectIsInvalid = !m_allObjects[uids[0]];
1032     if (!bExistsAlready || (bExistingObjectIsInvalid && isValidObject)) {
1033     m_allObjects[uids[0]] = obj;
1034     // recurse serialization for all members of this member
1035     // (only for struct/class types, noop for primitive types)
1036     SerializationRecursion<T_memberType>::serializeObject(this, nativeMember);
1037     }
1038     }
1039    
1040 schoenebeck 3775 /** @brief Serialize a C/C++ member variable allocated on the heap.
1041     *
1042     * This method is essentially used by applications in the same way as
1043     * @c serializeMember() above, however @c serializeMember() must only be
1044     * used for native C/C++ members which are variables that are memory
1045     * located within their owning parent data structures. For any variable
1046     * that's located on the RAM heap though, applications must use this
1047     * method instead to make it clear that there's no constant, static
1048     * offset relationship between the passed member and its owning parent.
1049     *
1050     * @discussion To avoid member name conflicts with native members, it is
1051     * recommended to always choose member names which would be impossible
1052     * as names to be declared in C/C++ code. For instance this framework
1053     * uses heap member names "[0]", "[1]", "[3]", ... in its out of the box
1054     * support when serializing elements of @c Array<> objects, since
1055     * brackets in general cannot be used as part of variable names in C++,
1056     * so using such or other special characters in heap member names, makes
1057     * such naming conflicts impossible.
1058     *
1059     * @param nativeObject - native C++ object to be registered for
1060     * serialization / deserialization
1061     * @param heapMember - C/C++ variable (located on the heap) of
1062     * @a nativeObject to be registered for
1063     * serialization / deserialization
1064     * @param memberName - name of @a heapMember to be stored with this
1065     * archive; an arbitrary but unique name should be
1066     * chosen which must not collide with names of
1067     * native members (see discussion above)
1068     * @see serializeMember() for native member variables
1069     */
1070     template<typename T_classType, typename T_memberType>
1071     void serializeHeapMember(const T_classType& nativeObject, const T_memberType& heapMember, const char* memberName) {
1072     const ssize_t offset = -1; // used for all members on heap
1073     const UIDChain uids = UIDChainResolver<T_memberType>(heapMember);
1074     const DataType type = DataType::dataTypeOf(heapMember);
1075     const Member member(memberName, uids[0], offset, type);
1076     const UID parentUID = UID::from(nativeObject);
1077     Object& parent = m_allObjects[parentUID];
1078     if (!parent) {
1079     const UIDChain uids = UIDChainResolver<T_classType>(nativeObject);
1080     const DataType type = DataType::dataTypeOf(nativeObject);
1081     parent = Object(uids, type);
1082     }
1083     parent.members().push_back(member);
1084     const Object obj(uids, type);
1085     const bool bExistsAlready = m_allObjects.count(uids[0]);
1086     const bool isValidObject = obj;
1087     const bool bExistingObjectIsInvalid = !m_allObjects[uids[0]];
1088     if (!bExistsAlready || (bExistingObjectIsInvalid && isValidObject)) {
1089     m_allObjects[uids[0]] = obj;
1090     // recurse serialization for all members of this member
1091     // (only for struct/class types, noop for primitive types)
1092     SerializationRecursion<T_memberType>::serializeObject(this, heapMember);
1093     }
1094     }
1095    
1096 schoenebeck 3183 /** @brief Set current version number for your C++ class.
1097 schoenebeck 3182 *
1098 schoenebeck 3183 * By calling this method you can define a version number for your
1099 schoenebeck 3182 * current C++ class (that is a version for its current data structure
1100 schoenebeck 3183 * layout and method implementations) that is going to be stored along
1101     * with the serialized archive. Only call this method if you really want
1102     * to constrain compatibility of your C++ class.
1103 schoenebeck 3182 *
1104     * Along with calling @c setMinVersion() this provides a way for you
1105 schoenebeck 3183 * to constrain backward compatibility regarding serialization and
1106     * deserialization of your C++ class which the Archive class will obey
1107     * to. If required, then typically you might do so in your
1108     * @c serialize() method implementation like:
1109 schoenebeck 3182 * @code
1110     * #define SRLZ(member) \
1111     * archive->serializeMember(*this, member, #member);
1112     *
1113 schoenebeck 3775 * struct Foo {
1114     * int a;
1115     * Bar b; // a custom struct or class having a serialize() method
1116     * std::string c;
1117     * std::vector<double> d;
1118     *
1119     * void serialize(Serialization::Archive* archive);
1120     * };
1121     *
1122 schoenebeck 3182 * void Foo::serialize(Serialization::Archive* archive) {
1123     * // when serializing: the current version of this class that is
1124     * // going to be stored with the serialized archive
1125     * archive->setVersion(*this, 6);
1126 schoenebeck 3183 * // when deserializing: the minimum version this C++ class is
1127     * // compatible with
1128 schoenebeck 3182 * archive->setMinVersion(*this, 3);
1129     * // actual data mebers to serialize / deserialize
1130     * SRLZ(a);
1131     * SRLZ(b);
1132     * SRLZ(c);
1133 schoenebeck 3775 * SRLZ(d);
1134 schoenebeck 3182 * }
1135     * @endcode
1136 schoenebeck 3183 * In this example above, the C++ class "Foo" would be serialized along
1137     * with the version number @c 6 and minimum version @c 3 as additional
1138     * meta information in the resulting archive (and its raw data stream
1139     * respectively).
1140 schoenebeck 3182 *
1141     * When deserializing archives with the example C++ class code above,
1142     * the Archive object would check whether your originally serialized
1143     * C++ "Foo" object had at least version number @c 3, if not the
1144     * deserialization process would automatically be stopped with a
1145     * @c Serialization::Exception, claiming that the classes are version
1146     * incompatible.
1147     *
1148 schoenebeck 3183 * But also consider the other way around: you might have serialized
1149     * your latest version of your C++ class, and might deserialize that
1150     * archive with an older version of your C++ class. In that case it will
1151     * likewise be checked whether the version of that old C++ class is at
1152     * least as high as the minimum version set with the already seralized
1153     * bleeding edge C++ class.
1154     *
1155 schoenebeck 3182 * Since this Serialization / deserialization framework is designed to
1156     * be robust on changes to your C++ classes and aims trying to
1157     * deserialize all your C++ objects correctly even if your C++ classes
1158     * have seen substantial software changes in the meantime; you might
1159 schoenebeck 3183 * sometimes see it as necessary to constrain backward compatibility
1160     * this way. Because obviously there are certain things this framework
1161     * can cope with, like for example that you renamed a data member while
1162     * keeping the layout consistent, or that you have added new members to
1163     * your C++ class or simply changed the order of your members in your
1164     * C++ class. But what this framework cannot detect is for example if
1165     * you changed the semantics of the values stored with your members, or
1166     * even substantially changed the algorithms in your class methods such
1167     * that they would not handle the data of your C++ members in the same
1168     * and correct way anymore.
1169 schoenebeck 3182 *
1170     * @param nativeObject - your C++ object you want to set a version for
1171     * @param v - the version number to set for your C++ class (by default,
1172     * that is if you do not explicitly call this method, then
1173     * your C++ object will be stored with version number @c 0 ).
1174     */
1175     template<typename T_classType>
1176     void setVersion(const T_classType& nativeObject, Version v) {
1177     const UID uid = UID::from(nativeObject);
1178     Object& obj = m_allObjects[uid];
1179     if (!obj) {
1180     const UIDChain uids = UIDChainResolver<T_classType>(nativeObject);
1181     const DataType type = DataType::dataTypeOf(nativeObject);
1182     obj = Object(uids, type);
1183     }
1184     setVersion(obj, v);
1185     }
1186    
1187     /** @brief Set a minimum version number for your C++ class.
1188     *
1189     * Call this method to define a minimum version that your current C++
1190     * class implementation would be compatible with when it comes to
1191 schoenebeck 3183 * deserialization of an archive containing an object of your C++ class.
1192     * Like the version information, the minimum version will also be stored
1193     * for objects of your C++ class with the resulting archive (and its
1194     * resulting raw data stream respectively).
1195 schoenebeck 3182 *
1196 schoenebeck 3183 * When you start to constrain version compatibility of your C++ class
1197     * you usually start by using 1 as version and 1 as minimum version.
1198     * So it is eligible to set the same number to both version and minimum
1199     * version. However you must @b not set a minimum version higher than
1200     * version. Doing so would not raise an exception, but the resulting
1201     * behavior would be undefined.
1202     *
1203     * It is not relevant whether you first set version and then minimum
1204     * version or vice versa. It is also not relevant when exactly you set
1205     * those two numbers, even though usually you would set both in your
1206     * serialize() method implementation.
1207     *
1208 schoenebeck 3182 * @see @c setVersion() for more details about this overall topic.
1209 schoenebeck 3183 *
1210     * @param nativeObject - your C++ object you want to set a version for
1211     * @param v - the minimum version you want to define for your C++ class
1212     * (by default, that is if you do not explicitly call this
1213     * method, then a minium version of @c 0 is assumed for your
1214     * C++ class instead).
1215 schoenebeck 3182 */
1216     template<typename T_classType>
1217     void setMinVersion(const T_classType& nativeObject, Version v) {
1218     const UID uid = UID::from(nativeObject);
1219     Object& obj = m_allObjects[uid];
1220     if (!obj) {
1221     const UIDChain uids = UIDChainResolver<T_classType>(nativeObject);
1222     const DataType type = DataType::dataTypeOf(nativeObject);
1223     obj = Object(uids, type);
1224     }
1225     setMinVersion(obj, v);
1226     }
1227    
1228 schoenebeck 3138 virtual void decode(const RawData& data);
1229     virtual void decode(const uint8_t* data, size_t size);
1230     void clear();
1231 schoenebeck 3150 bool isModified() const;
1232 schoenebeck 3153 void removeMember(Object& parent, const Member& member);
1233 schoenebeck 3138 void remove(const Object& obj);
1234     Object& rootObject();
1235     Object& objectByUID(const UID& uid);
1236 schoenebeck 3150 void setAutoValue(Object& object, String value);
1237     void setIntValue(Object& object, int64_t value);
1238     void setRealValue(Object& object, double value);
1239     void setBoolValue(Object& object, bool value);
1240     void setEnumValue(Object& object, uint64_t value);
1241 schoenebeck 3771 void setStringValue(Object& object, String value);
1242 schoenebeck 3150 String valueAsString(const Object& object);
1243 schoenebeck 3169 int64_t valueAsInt(const Object& object);
1244     double valueAsReal(const Object& object);
1245     bool valueAsBool(const Object& object);
1246 schoenebeck 3182 void setVersion(Object& object, Version v);
1247     void setMinVersion(Object& object, Version v);
1248 schoenebeck 3156 String name() const;
1249     void setName(String name);
1250     String comment() const;
1251     void setComment(String comment);
1252     time_t timeStampCreated() const;
1253     time_t timeStampModified() const;
1254     tm dateTimeCreated(time_base_t base = LOCAL_TIME) const;
1255     tm dateTimeModified(time_base_t base = LOCAL_TIME) const;
1256 schoenebeck 3774 operation_t operation() const;
1257 schoenebeck 3138
1258     protected:
1259     // UID resolver for non-pointer types
1260     template<typename T>
1261     class UIDChainResolver {
1262     public:
1263     UIDChainResolver(const T& data) {
1264     m_uid.push_back(UID::from(data));
1265     }
1266    
1267     operator UIDChain() const { return m_uid; }
1268     UIDChain operator()() const { return m_uid; }
1269     private:
1270     UIDChain m_uid;
1271     };
1272    
1273     // UID resolver for pointer types (of 1st degree)
1274     template<typename T>
1275     class UIDChainResolver<T*> {
1276     public:
1277     UIDChainResolver(const T*& data) {
1278 schoenebeck 3168 const UID uids[2] = {
1279     { &data, sizeof(data) },
1280     { data, sizeof(*data) }
1281     };
1282     m_uid.push_back(uids[0]);
1283     m_uid.push_back(uids[1]);
1284 schoenebeck 3138 }
1285    
1286     operator UIDChain() const { return m_uid; }
1287     UIDChain operator()() const { return m_uid; }
1288     private:
1289     UIDChain m_uid;
1290     };
1291    
1292     // SerializationRecursion for non-pointer class/struct types.
1293     template<typename T, bool T_isRecursive>
1294     struct SerializationRecursionImpl {
1295     static void serializeObject(Archive* archive, const T& obj) {
1296     const_cast<T&>(obj).serialize(archive);
1297     }
1298     };
1299    
1300     // SerializationRecursion for pointers (of 1st degree) to class/structs.
1301     template<typename T, bool T_isRecursive>
1302     struct SerializationRecursionImpl<T*,T_isRecursive> {
1303     static void serializeObject(Archive* archive, const T*& obj) {
1304     if (!obj) return;
1305     const_cast<T*&>(obj)->serialize(archive);
1306     }
1307     };
1308    
1309     // NOOP SerializationRecursion for primitive types.
1310     template<typename T>
1311     struct SerializationRecursionImpl<T,false> {
1312     static void serializeObject(Archive* archive, const T& obj) {}
1313     };
1314    
1315     // NOOP SerializationRecursion for pointers (of 1st degree) to primitive types.
1316     template<typename T>
1317     struct SerializationRecursionImpl<T*,false> {
1318     static void serializeObject(Archive* archive, const T*& obj) {}
1319     };
1320    
1321 schoenebeck 3771 // NOOP SerializationRecursion for String objects.
1322 schoenebeck 3772 template<bool T_isRecursive>
1323     struct SerializationRecursionImpl<String,T_isRecursive> {
1324 schoenebeck 3771 static void serializeObject(Archive* archive, const String& obj) {}
1325     };
1326    
1327     // NOOP SerializationRecursion for String pointers (of 1st degree).
1328 schoenebeck 3772 template<bool T_isRecursive>
1329     struct SerializationRecursionImpl<String*,T_isRecursive> {
1330 schoenebeck 3771 static void serializeObject(Archive* archive, const String*& obj) {}
1331     };
1332    
1333 schoenebeck 3775 // SerializationRecursion for Array<> objects.
1334     template<typename T, bool T_isRecursive>
1335     struct SerializationRecursionImpl<Array<T>,T_isRecursive> {
1336     static void serializeObject(Archive* archive, const Array<T>& obj) {
1337     const UIDChain uids = UIDChainResolver<Array<T>>(obj);
1338     const Object& object = archive->objectByUID(uids[0]);
1339     if (archive->operation() == OPERATION_SERIALIZE) {
1340     for (size_t i = 0; i < obj.size(); ++i) {
1341     archive->serializeHeapMember(
1342 schoenebeck 3776 obj, obj[i], ("[" + toString(i) + "]").c_str()
1343 schoenebeck 3775 );
1344     }
1345     } else {
1346     const_cast<Object&>(object).m_sync =
1347     [&obj,archive](Object& dstObj, const Object& srcObj,
1348     void* syncer)
1349     {
1350     const size_t n = srcObj.members().size();
1351     const_cast<Array<T>&>(obj).resize(n);
1352     for (size_t i = 0; i < obj.size(); ++i) {
1353     archive->serializeHeapMember(
1354 schoenebeck 3776 obj, obj[i], ("[" + toString(i) + "]").c_str()
1355 schoenebeck 3775 );
1356     }
1357     // updating dstObj required as serializeHeapMember()
1358     // replaced the original object by a new one
1359     dstObj = archive->objectByUID(dstObj.uid());
1360     for (size_t i = 0; i < obj.size(); ++i) {
1361 schoenebeck 3776 String name = "[" + toString(i) + "]";
1362 schoenebeck 3775 Member srcMember = srcObj.memberNamed(name);
1363     Member dstMember = dstObj.memberNamed(name);
1364     ((Syncer*)syncer)->syncMember(dstMember, srcMember);
1365     }
1366     };
1367     }
1368     }
1369     };
1370    
1371     // SerializationRecursion for Array<> pointers (of 1st degree).
1372     template<typename T, bool T_isRecursive>
1373     struct SerializationRecursionImpl<Array<T>*,T_isRecursive> {
1374     static void serializeObject(Archive* archive, const Array<T>*& obj) {
1375     if (!obj) return;
1376     SerializationRecursionImpl<Array<T>,T_isRecursive>::serializeObject(
1377     archive, *obj
1378     );
1379     }
1380     };
1381    
1382 schoenebeck 3776 // SerializationRecursion for Set<> objects.
1383     template<typename T, bool T_isRecursive>
1384     struct SerializationRecursionImpl<Set<T>,T_isRecursive> {
1385     static void serializeObject(Archive* archive, const Set<T>& obj) {
1386     const UIDChain uids = UIDChainResolver<Set<T>>(obj);
1387     const Object& object = archive->objectByUID(uids[0]);
1388     if (archive->operation() == OPERATION_SERIALIZE) {
1389     for (const T& key : obj) {
1390     archive->serializeHeapMember(
1391     obj, key, ("[" + toString(key) + "]").c_str()
1392     );
1393     }
1394     } else {
1395     const_cast<Object&>(object).m_sync =
1396     [&obj,archive](Object& dstObj, const Object& srcObj,
1397     void* syncer)
1398     {
1399     const size_t n = srcObj.members().size();
1400     const_cast<Set<T>&>(obj).clear();
1401     for (size_t i = 0; i < n; ++i) {
1402     const Member& member = srcObj.members()[i];
1403     String name = member.name();
1404     if (name.length() < 2 || name[0] != '[' ||
1405     *name.rbegin() != ']') continue;
1406     name = name.substr(1, name.length() - 2);
1407     T key;
1408     const UIDChain uids = UIDChainResolver<T>(key);
1409     const DataType type = DataType::dataTypeOf(key);
1410     Object tmpObj(uids, type);
1411     tmpObj.setNativeValueFromString(name);
1412     const_cast<Set<T>&>(obj).insert(key);
1413     }
1414     for (const T& key : obj) {
1415     archive->serializeHeapMember(
1416     obj, key, ("[" + toString(key) + "]").c_str()
1417     );
1418     }
1419     // updating dstObj required as serializeHeapMember()
1420     // replaced the original object by a new one
1421     dstObj = archive->objectByUID(dstObj.uid());
1422     };
1423     }
1424     }
1425     };
1426    
1427     // SerializationRecursion for Set<> pointers (of 1st degree).
1428     template<typename T, bool T_isRecursive>
1429     struct SerializationRecursionImpl<Set<T>*,T_isRecursive> {
1430     static void serializeObject(Archive* archive, const Set<T>*& obj) {
1431     if (!obj) return;
1432     SerializationRecursionImpl<Set<T>,T_isRecursive>::serializeObject(
1433     archive, *obj
1434     );
1435     }
1436     };
1437    
1438 schoenebeck 3138 // Automatically handles recursion for class/struct types, while ignoring all primitive types.
1439     template<typename T>
1440 schoenebeck 3167 struct SerializationRecursion : SerializationRecursionImpl<T, LIBGIG_IS_CLASS(T)> {
1441 schoenebeck 3138 };
1442    
1443     class ObjectPool : public std::map<UID,Object> {
1444     public:
1445     // prevent passing obvious invalid UID values from creating a new pair entry
1446     Object& operator[](const UID& k) {
1447     static Object invalid;
1448     if (!k.isValid()) {
1449     invalid = Object();
1450     return invalid;
1451     }
1452     return std::map<UID,Object>::operator[](k);
1453     }
1454     };
1455    
1456     friend String _encode(const ObjectPool& objects);
1457    
1458     private:
1459     String _encodeRootBlob();
1460     void _popRootBlob(const char*& p, const char* end);
1461     void _popObjectsBlob(const char*& p, const char* end);
1462    
1463     protected:
1464 schoenebeck 3775 /** @brief Synchronizes 2 archives with each other.
1465     *
1466     * This class is used internally at final stage of deserialization. It
1467     * is not used for serialization.
1468     *
1469     * The deserialization algorithm of this framework works like this:
1470     *
1471     * 1. The (currently running) application constructs an @c Archive
1472     * object by passing a previously serialized raw data stream to the
1473     * @c Archive constructor. At this stage, the raw data stream is
1474     * decoded and this archive's object pool is populated with objects,
1475     * which in turn are filled with data (at temporary storage
1476     * location inside the respective @c Object instances) of the decoded
1477     * data stream.
1478     *
1479     * 2. A temporary (2nd) @c Archive object is constructed internally by
1480     * this framework, reflecting the (currently running) application's
1481     * latest data structre layout, without touching any actual data yet.
1482     * The individual @c Object instances of this 2nd @c Archive are
1483     * bound to the running application's native (target) C/C++ member
1484     * variables to be updated (written to) next.
1485     *
1486     * Note that at this point, the 2 archives might very well have quite
1487     * different data structure layouts, due to potential software changes
1488     * between the original serializing application and this currently
1489     * deserializing software application.
1490     *
1491     * 3. This Syncer class is used to transfer the actual data from the 1st
1492     * to the 2nd (temporary) @c Archive object, it does so by traversing
1493     * the 2 archives' object pools, trying to find the respective 2
1494     * objects on the two sides to be synchronized, and if found, it
1495     * transfers the data from the 1st archive's object to the 2nd
1496     * archive's object, effectively writing to the currently running
1497     * application's final C/C++ memory locations.
1498     *
1499     * This 3 staged approach allows to deserialize data in a much more
1500     * relaxed, adaptive and flexible (while still automatic) way than
1501     * traditional serialization frameworks would do.
1502     */
1503 schoenebeck 3138 class Syncer {
1504     public:
1505     Syncer(Archive& dst, Archive& src);
1506     void syncObject(const Object& dst, const Object& src);
1507     void syncPrimitive(const Object& dst, const Object& src);
1508 schoenebeck 3771 void syncString(const Object& dst, const Object& src);
1509 schoenebeck 3775 void syncArray(const Object& dst, const Object& src);
1510 schoenebeck 3776 void syncSet(const Object& dst, const Object& src);
1511 schoenebeck 3138 void syncPointer(const Object& dst, const Object& src);
1512     void syncMember(const Member& dstMember, const Member& srcMember);
1513 schoenebeck 3775 protected:
1514 schoenebeck 3138 static Member dstMemberMatching(const Object& dstObj, const Object& srcObj, const Member& srcMember);
1515     private:
1516     Archive& m_dst;
1517     Archive& m_src;
1518     };
1519    
1520     virtual void encode();
1521    
1522     ObjectPool m_allObjects;
1523     operation_t m_operation;
1524     UID m_root;
1525     RawData m_rawData;
1526 schoenebeck 3150 bool m_isModified;
1527 schoenebeck 3156 String m_name;
1528     String m_comment;
1529     time_t m_timeCreated;
1530     time_t m_timeModified;
1531 schoenebeck 3138 };
1532    
1533     /**
1534     * Will be thrown whenever an error occurs during an serialization or
1535     * deserialization process.
1536     */
1537     class Exception {
1538     public:
1539     String Message;
1540    
1541 schoenebeck 3198 Exception(String format, ...);
1542     Exception(String format, va_list arg);
1543 schoenebeck 3138 void PrintMessage();
1544     virtual ~Exception() {}
1545 schoenebeck 3198
1546     protected:
1547     Exception();
1548     static String assemble(String format, va_list arg);
1549 schoenebeck 3138 };
1550    
1551     } // namespace Serialization
1552    
1553     #endif // LIBGIG_SERIALIZATION_H

  ViewVC Help
Powered by ViewVC