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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3777 - (show annotations) (download) (as text)
Sat May 23 19:55:32 2020 UTC (3 years, 10 months 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 /***************************************************************************
2 * *
3 * Copyright (C) 2017-2020 Christian Schoenebeck *
4 * <cuse@users.sourceforge.net> *
5 * *
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 #include <set>
38 #include <time.h>
39 #include <stdarg.h>
40 #include <assert.h>
41 #include <functional>
42
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 # include <tr1/type_traits>
65 # 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 #endif
69
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 // just symbol prototyping
116 class DataType;
117 class Object;
118 class Member;
119 class Archive;
120 class ObjectPool;
121 class Exception;
122
123 /** @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 typedef std::string String;
131
132 /** @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 /** @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 /** @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 /** @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 typedef std::vector<uint8_t> RawData;
189
190 /** @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 typedef void* ID;
201
202 /** @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 typedef uint32_t Version;
210
211 /** @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 enum time_base_t {
217 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 };
220
221 /** @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 template<typename T>
229 bool IsEnum(const T& data) {
230 #if !HAS_BUILTIN_TYPE_TRAITS
231 return std::tr1::is_enum<T>::value;
232 #else
233 return __is_enum(T);
234 #endif
235 }
236
237 /** @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 template<typename T>
248 bool IsUnion(const T& data) {
249 #if !HAS_BUILTIN_TYPE_TRAITS
250 return false; // without compiler support we cannot distinguish union from class
251 #else
252 return __is_union(T);
253 #endif
254 }
255
256 /** @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 template<typename T>
266 bool IsClass(const T& data) {
267 #if !HAS_BUILTIN_TYPE_TRAITS
268 return std::tr1::__is_union_or_class<T>::value; // without compiler support we cannot distinguish union from class
269 #else
270 return __is_class(T);
271 #endif
272 }
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 /*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 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 /** @brief Unique identifier referring to one specific native C++ object, member, fundamental variable, or any other native C++ data.
305 *
306 * 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 *
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 */
319 class UID {
320 public:
321 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
324 bool isValid() const;
325 operator bool() const { return isValid(); } ///< Same as calling isValid().
326 //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 /** @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 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 const UID uid = { (ID) &obj, sizeof(obj) };
350 return uid;
351 }
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 const UID uid = { (ID) obj, sizeof(*obj) };
359 return uid;
360 }
361 };
362 };
363
364 /**
365 * Reflects an invalid UID and behaves similar to NULL as invalid value for
366 * pointer types. All UID objects are first initialized with this value,
367 * and it essentially an all zero object.
368 */
369 extern const UID NO_UID;
370
371 /** @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 typedef std::vector<UID> UIDChain;
403
404 #if LIBGIG_SERIALIZATION_INTERNAL
405 // prototyping of private internal friend functions
406 static String _encodePrimitiveValue(const Object& obj);
407 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 static String _primitiveObjectValueToString(const Object& obj);
412 // |
413 template<typename T>
414 static T _primitiveObjectValueToNumber(const Object& obj);
415 #endif // LIBGIG_SERIALIZATION_INTERNAL
416
417 /** @brief Abstract reflection of a native C++ data type.
418 *
419 * 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 */
433 class DataType {
434 public:
435 DataType();
436 size_t size() const { return m_size; } ///< Returns native memory size of the respective C++ object or variable.
437 bool isValid() const;
438 bool isPointer() const;
439 bool isClass() const;
440 bool isPrimitive() const;
441 bool isString() const;
442 bool isInteger() const;
443 bool isReal() const;
444 bool isBool() const;
445 bool isEnum() const;
446 bool isArray() const;
447 bool isSet() const;
448 bool isMap() const;
449 bool isSigned() const;
450 operator bool() const { return isValid(); } ///< Same as calling isValid().
451 //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 String baseTypeName() const;
458 String customTypeName(bool demangle = false) const;
459 String customTypeName2(bool demangle = false) const;
460
461 /** @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 template<typename T>
472 static DataType dataTypeOf(const T& data) {
473 return Resolver<T>::resolve(data);
474 }
475
476 protected:
477 DataType(bool isPointer, int size, String baseType,
478 String customType1 = "", String customType2 = "");
479
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 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 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 if (type == typeid(String)) return DataType(T_isPointer, sz, "String");
515
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 // DataType resolver for non-pointer Array<> container object types.
541 template<typename T>
542 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 // DataType resolver for non-pointer Set<> container object types.
561 template<typename T>
562 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 // 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 template<typename T>
605 static String rawCppTypeNameOf(const T& data) {
606 #if defined _MSC_VER // Microsoft compiler ...
607 String name = typeid(data).raw_name();
608 #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 String m_customTypeName2;
620 int m_size;
621 bool m_isPointer;
622
623 #if LIBGIG_SERIALIZATION_INTERNAL
624 friend DataType _popDataTypeBlob(const char*& p, const char* end);
625 #endif
626 friend class Archive;
627 };
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 *
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 */
650 class Member {
651 public:
652 Member();
653 UID uid() const;
654 String name() const;
655 ssize_t offset() const;
656 const DataType& type() const;
657 bool isValid() const;
658 operator bool() const { return isValid(); } ///< Same as calling isValid().
659 //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 Member(String name, UID uid, ssize_t offset, DataType type);
667 friend class Archive;
668
669 private:
670 UID m_uid;
671 ssize_t m_offset;
672 String m_name;
673 DataType m_type;
674
675 #if LIBGIG_SERIALIZATION_INTERNAL
676 friend Member _popMemberBlob(const char*& p, const char* end);
677 #endif
678 };
679
680 /** @brief Abstract reflection of some native serialized C/C++ data.
681 *
682 * 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 *
689 * 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 * 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 * instead. This design decision was made for performance and safety
700 * reasons.
701 *
702 * @see Archive::setIntValue() as an example for modifying Object instances.
703 */
704 class Object {
705 public:
706 Object();
707 Object(UIDChain uidChain, DataType type);
708
709 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 bool isVersionCompatibleTo(const Object& other) const;
716 std::vector<Member>& members();
717 const std::vector<Member>& members() const;
718 Member memberNamed(String name) const;
719 Member memberByUID(const UID& uid) const;
720 std::vector<Member> membersOfType(const DataType& type) const;
721 int sequenceIndexOf(const Member& member) const;
722 bool isValid() const;
723 operator bool() const { return isValid(); } ///< Same as calling isValid().
724 //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 void setNativeValueFromString(const String& s);
730
731 protected:
732 void remove(const Member& member);
733 void setVersion(Version v);
734 void setMinVersion(Version v);
735
736 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 std::function<void(Object& dstObj, const Object& srcObj, void* syncer)> m_sync;
744
745 #if LIBGIG_SERIALIZATION_INTERNAL
746 friend String _encodePrimitiveValue(const Object& obj);
747 friend Object _popObjectBlob(const char*& p, const char* end);
748 friend void _popPrimitiveValue(const char*& p, const char* end, Object& obj);
749 friend String _primitiveObjectValueToString(const Object& obj);
750 // |
751 template<typename T>
752 friend T _primitiveObjectValueToNumber(const Object& obj);
753 #endif // LIBGIG_SERIALIZATION_INTERNAL
754
755 friend class Archive;
756 };
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 * @endcode
770 * Or if you prefer the look of operator based code:
771 * @code
772 * Archive a;
773 * a << myRootObject;
774 * @endcode
775 * 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 * @endcode
790 * Or with operator instead:
791 * @code
792 * Archive a(rawDataStream);
793 * a >> myRootObject;
794 * @endcode
795 * Now this framework automatically handles serialization and
796 * 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 * @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 * std::string text;
813 * std::vector<int> v;
814 * };
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 * std::set<int> someSet;
823 * std::map<double,Foo> someMap;
824 * };
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 * std::string text;
835 * std::vector<int> v;
836 *
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 * std::set<int> someSet;
847 * std::map<double,Foo> someMap;
848 *
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 * SRLZ(text);
863 * SRLZ(v);
864 * }
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 * SRLZ(someSet);
873 * SRLZ(someMap);
874 * }
875 * @endcode
876 * And that's it!
877 *
878 * 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 * Note that there is only one method that you need to implement. So the
884 * 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 *
891 * In case you need to enforce backward incompatibility for one of your C++
892 * 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 */
895 class Archive {
896 public:
897 /** @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 Archive();
906 Archive(const RawData& data);
907 Archive(const uint8_t* data, size_t size);
908 virtual ~Archive();
909
910 /** @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 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 /** @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 template<typename T>
971 void deserialize(T* obj) {
972 Archive a;
973 a.m_operation = m_operation = OPERATION_DESERIALIZE;
974 obj->serialize(&a);
975 a.m_root = UID::from(obj);
976 Syncer s(a, *this);
977 a.m_operation = m_operation = OPERATION_NONE;
978 }
979
980 /** @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 template<typename T>
995 void operator<<(const T& obj) {
996 serialize(&obj);
997 }
998
999 /** @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 template<typename T>
1018 void operator>>(T& obj) {
1019 deserialize(&obj);
1020 }
1021
1022 const RawData& rawData();
1023 virtual String rawDataFormat() const;
1024
1025 /** @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 * 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 * void Foo::serialize(Serialization::Archive* archive) {
1047 * SRLZ(a);
1048 * SRLZ(b);
1049 * SRLZ(c);
1050 * SRLZ(d);
1051 * }
1052 * @endcode
1053 * As you can see, using such a macro makes your code more readable,
1054 * compact and less error prone.
1055 *
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 * @see serializeHeapMember() for variables on the RAM heap
1080 */
1081 template<typename T_classType, typename T_memberType>
1082 void serializeMember(const T_classType& nativeObject, const T_memberType& nativeMember, const char* memberName) {
1083 const ssize_t offset =
1084 ((const uint8_t*)(const void*)&nativeMember) -
1085 ((const uint8_t*)(const void*)&nativeObject);
1086 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 /** @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 * uses heap member names "[0]", "[1]", "[2]", ... in its out of the box
1123 * 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 /** @brief Set current version number for your C++ class.
1166 *
1167 * By calling this method you can define a version number for your
1168 * current C++ class (that is a version for its current data structure
1169 * 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 *
1173 * Along with calling @c setMinVersion() this provides a way for you
1174 * 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 * @code
1179 * #define SRLZ(member) \
1180 * archive->serializeMember(*this, member, #member);
1181 *
1182 * 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 * 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 * // when deserializing: the minimum version this C++ class is
1196 * // compatible with
1197 * archive->setMinVersion(*this, 3);
1198 * // actual data mebers to serialize / deserialize
1199 * SRLZ(a);
1200 * SRLZ(b);
1201 * SRLZ(c);
1202 * SRLZ(d);
1203 * }
1204 * @endcode
1205 * 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 *
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 * 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 * 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 * 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 *
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 * 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 *
1265 * 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 * @see @c setVersion() for more details about this overall topic.
1278 *
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 */
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 virtual void decode(const RawData& data);
1298 virtual void decode(const uint8_t* data, size_t size);
1299 void clear();
1300 bool isModified() const;
1301 void removeMember(Object& parent, const Member& member);
1302 void remove(const Object& obj);
1303 Object& rootObject();
1304 Object& objectByUID(const UID& uid);
1305 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 void setStringValue(Object& object, String value);
1311 String valueAsString(const Object& object);
1312 int64_t valueAsInt(const Object& object);
1313 double valueAsReal(const Object& object);
1314 bool valueAsBool(const Object& object);
1315 void setVersion(Object& object, Version v);
1316 void setMinVersion(Object& object, Version v);
1317 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 operation_t operation() const;
1326
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 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 }
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 // NOOP SerializationRecursion for String objects.
1391 template<bool T_isRecursive>
1392 struct SerializationRecursionImpl<String,T_isRecursive> {
1393 static void serializeObject(Archive* archive, const String& obj) {}
1394 };
1395
1396 // NOOP SerializationRecursion for String pointers (of 1st degree).
1397 template<bool T_isRecursive>
1398 struct SerializationRecursionImpl<String*,T_isRecursive> {
1399 static void serializeObject(Archive* archive, const String*& obj) {}
1400 };
1401
1402 // 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 obj, obj[i], ("[" + toString(i) + "]").c_str()
1412 );
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 obj, obj[i], ("[" + toString(i) + "]").c_str()
1424 );
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 String name = "[" + toString(i) + "]";
1431 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 // 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 // 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 // Automatically handles recursion for class/struct types, while ignoring all primitive types.
1569 template<typename T>
1570 struct SerializationRecursion : SerializationRecursionImpl<T, LIBGIG_IS_CLASS(T)> {
1571 };
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 /** @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 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 void syncString(const Object& dst, const Object& src);
1639 void syncArray(const Object& dst, const Object& src);
1640 void syncSet(const Object& dst, const Object& src);
1641 void syncMap(const Object& dst, const Object& src);
1642 void syncPointer(const Object& dst, const Object& src);
1643 void syncMember(const Member& dstMember, const Member& srcMember);
1644 protected:
1645 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 bool m_isModified;
1658 String m_name;
1659 String m_comment;
1660 time_t m_timeCreated;
1661 time_t m_timeModified;
1662 };
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 Exception(String format, ...);
1673 Exception(String format, va_list arg);
1674 void PrintMessage();
1675 virtual ~Exception() {}
1676
1677 protected:
1678 Exception();
1679 static String assemble(String format, va_list arg);
1680 };
1681
1682 } // namespace Serialization
1683
1684 #endif // LIBGIG_SERIALIZATION_H

  ViewVC Help
Powered by ViewVC