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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3776 - (show annotations) (download) (as text)
Sat May 23 19:26:07 2020 UTC (3 years, 10 months ago) by schoenebeck
File MIME type: text/x-c++hdr
File size: 69548 byte(s)
* Serialization.cpp/.h: Added built-in support for C++ Set<>
  objects (a.k.a. std::set from the STL).

* Bumped version (4.2.0.svn14).

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

  ViewVC Help
Powered by ViewVC