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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 4093 - (hide annotations) (download) (as text)
Mon Feb 12 12:26:06 2024 UTC (2 months, 1 week ago) by schoenebeck
File MIME type: text/x-c++hdr
File size: 76042 byte(s)
* Move system dependent type and macro definitions into a shared header
  file sysdef.h (fixes compilation error with MSVC).

* Bumped version (4.4.0.svn2).

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

  ViewVC Help
Powered by ViewVC