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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

* Bumped version (4.4.0.svn2).

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

  ViewVC Help
Powered by ViewVC