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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3183 - (hide annotations) (download) (as text)
Mon May 15 18:44:32 2017 UTC (3 years, 4 months ago) by schoenebeck
File MIME type: text/x-c++hdr
File size: 51378 byte(s)
* Wrote API documentation for entire new Serialization
  framework.
* Hide some of the method implementation of the Serialization
  framework.

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

  ViewVC Help
Powered by ViewVC