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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3775 - (show annotations) (download) (as text)
Tue May 19 15:23:11 2020 UTC (3 years, 11 months ago) by schoenebeck
File MIME type: text/x-c++hdr
File size: 65117 byte(s)
Serialization.cpp/.h: Added built-in support for C++ Array<> objects

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

* Member offsets are now signed and for (newly added support of) member
  variables on the heap -1 is always used as offset instead.

* Bumped version (4.2.0.svn13).

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

  ViewVC Help
Powered by ViewVC