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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3777 - (hide annotations) (download) (as text)
Sat May 23 19:55:32 2020 UTC (4 years ago) by schoenebeck
File MIME type: text/x-c++hdr
File size: 76021 byte(s)
Serialization.cpp/.h: Added built-in support for C++ Map<> objects

* Introduced out of the box support for serialising / deserialising
  variables of C++ Map<> data types (a.k.a. std::map from the STL).

* DataType: Added optional 2nd custom type name.

* Bumped version (4.2.0.svn15).

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

  ViewVC Help
Powered by ViewVC