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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3774 - (show annotations) (download) (as text)
Tue May 19 14:55:48 2020 UTC (3 years, 11 months ago) by schoenebeck
File MIME type: text/x-c++hdr
File size: 53881 byte(s)
* Serialization.cpp/.h: Added new method Archive::operation() which
  allows applications to distinguish between serialization vs.
  deserialization in their serialize() method implementations.

* Bumped version (4.2.0.svn12).

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

  ViewVC Help
Powered by ViewVC