1 /**
2  * The iz serialization system.
3  */
4 module iz.serializer;
5 
6 import
7     std.range, std.typetuple, std.conv, std.traits, std.stdio;
8 import
9     iz.memory, iz.containers, iz.strings, iz.rtti, iz.sugar;
10 
11 public
12 {
13     import iz.types, iz.properties, iz.referencable, iz.streams;
14 }
15 
16 // Serializable types validator & misc. ---------------------------------------+
17 
18 /**
19  * Makes a reference serializable.
20  *
21  * The reference must be stored in the ReferenceMan.
22  *
23  * A "referenced variable" is typically something that is assigned
24  * at the run-time but not owned by the entity that wants to keep track of it,
25  * or that is serialized by another entity.
26  *
27  * Note that this class is not needed to serialize a reference to an object or to
28  * a delegate, since those reference types are automatically handled by the serializer.
29  */
30 // class inherited from the old serialization system, not really needed anymore.
31 class SerializableReference: PropertyPublisher
32 {
33 
34     mixin PropertyPublisherImpl;
35     mixin inheritedDtor;
36 
37 protected:
38 
39     ubyte _cnt;
40     Array!char _id, _tp;
41     void delegate(Object) _onRestored;
42 
43     void doSet()
44     {
45         _cnt++;
46         if (_cnt == 2)
47         {
48             _cnt = 0;
49             if (_onRestored)
50                 _onRestored(this);
51         }
52     }
53 
54 @PropHints(PropHint.initCare):
55 
56     @Get char[] type()
57     {
58         return _tp[];
59     }
60 
61     @Set void type(char[] value)
62     {
63         _tp = value;
64         doSet;
65     }
66 
67     @Get char[] identifier()
68     {
69          return _id[];
70     }
71 
72     @Set void identifier(char[] value)
73     {
74         _id = value;
75         doSet;
76     }
77 
78 public:
79 
80     ///
81     this() {collectPublications!SerializableReference;}
82 
83     ~this()
84     {
85         destruct(_tp);
86         destruct(_id);
87     }
88 
89     /**
90      * Sets the internal fields according to a referenced.
91      *
92      * Usually called before the serialization.
93      */
94     void storeReference(RT)(ref RT reference)
95     {
96         _tp = RT.stringof.dup;
97         _id = ReferenceMan.referenceID!RT(reference).dup;
98     }
99 
100     /**
101      * Returns the reference according to the internal fields.
102      *
103      * Usually called after the deserialization of after the
104      * the reference owner is notified by onRestored().
105      */
106     auto restoreReference(RT)()
107     {
108         return ReferenceMan.reference!RT(_id);
109     }
110 
111     /**
112      * Defines the event called when the the identifier string and the
113      * type string are restored, so that the reference owner can
114      * retrieve the matching reference in the ReferenceMan.
115      */
116     void onRestored(void delegate(Object) value)
117     {
118         _onRestored = value;
119     }
120     /// ditto
121     void delegate(Object) onRestored()
122     {
123         return _onRestored;
124     }
125 }
126 
127 package bool isSerObjectType(T)()
128 {
129     static if (is(T : Stream)) return false;
130     else static if (is(T : Object)) return true;
131     else return false;
132 }
133 
134 package bool isSerObjectType(RtType type)
135 {
136     with(RtType) return type == _object;
137 }
138 
139 package bool isSerArrayType(T)()
140 {
141     static if (!isArray!T) return false;
142     else static if (isMultiDimensionalArray!T) return false;
143     else static if (true)
144     {
145         alias TT = typeof(T.init[0]);
146         static if (isSomeFunction!TT) return false;
147         else static if (isNarrowString!TT) return true;
148         else static if (!isBasicRtType!TT) return false;
149         else return true;
150     }
151     else return true;
152 }
153 
154 /**
155  * Only a subset of the type representable as a Rtti are serializable.
156  * This template only evaluates to true if it's the case.
157  */
158 bool isSerializable(T)()
159 {
160     static if (isBasicRtType!T) return true;
161     else static if (isSerArrayType!T) return true;
162     else static if (is(T : Stream)) return true;
163     else static if (isSerObjectType!T) return true;
164     else static if (is(T==delegate)) return true;
165     else static if (is(PointerTarget!T==function)) return true;
166     else static if (is(T==GenericEnum)) return true;
167     else static if (is(T==GenericStruct)) return true;
168     else return false;
169 }
170 
171 unittest
172 {
173     struct S{}
174     struct V{uint _value; alias _value this;}
175     struct VS{V _value; alias _value this;}
176     static assert( isSerializable!ubyte );
177     static assert( isSerializable!double );
178     static assert( isSerializable!(ushort[]) );
179     static assert( isSerializable!Object );
180     static assert( !isSerializable!S );
181     static assert( !isSerializable!VS );
182     static assert( isSerializable!MemoryStream);
183     static assert( isSerializable!GenericDelegate);
184     static assert( !isSerializable!(int[int]) );
185 }
186 // -----------------------------------------------------------------------------
187 
188 // Intermediate Serialization Tree --------------------------------------------+
189 
190 /// Represents a serializable property without genericity.
191 struct SerNodeInfo
192 {
193     /// the rtti of the property
194     const(Rtti)* rtti;
195     /// a pointer to a $(D PropDescriptor)
196     Ptr     descriptor;
197     /// the raw value
198     Array!ubyte value;
199     /// the name of the property
200     Array!char  name;
201     /// the property level in the IST
202     uint    level;
203     /// indicates if any error occured during processing
204     bool    isDamaged;
205     /// hint to rebuild the IST
206     bool    isLastChild;
207 
208     ~this()
209     {
210         destruct(value);
211         destruct(name);
212     }
213 }
214 
215 /**
216  * Stores an IST in an associative array.
217  */
218 struct IstNodeCache
219 {
220 
221 private:
222 
223     IstNode _root;
224     HashMap_AB!(const(char)[], IstNode) _aa;
225 
226 public:
227 
228     @disable this(this);
229 
230     ~this()
231     {
232         destruct(_aa);
233     }
234 
235     /// Updates the cache.
236     void setRoot(IstNode root)
237     {
238         assert(root);
239         _root = root;
240         _aa.clear;
241 
242         size_t count;
243         deepIterate!((a => ++count), "children")(root);
244         _aa.reserve(count);
245         deepIterate!(a => _aa.insert!imReserved(a.identifiersChain(), a), "children")(root);
246     }
247 
248     /// Returns: The node with that matches to an identifier chain.
249     IstNode find(const(char[]) identChain)
250     {
251         if (IstNode* n = identChain in _aa)
252             return *n;
253         else
254             return null;
255     }
256 }
257 
258 /// IST node
259 class IstNode
260 {
261 
262     mixin TreeItem;
263 
264 private:
265 
266     SerNodeInfo _info;
267 
268 public:
269 
270     ~this()
271     {
272         _info.name.length = 0;
273         _info.value.length = 0;
274         destruct(_info);
275         deleteChildren;
276     }
277 
278     /**
279      * Returns a new unmanaged IstNode.
280      */
281     IstNode addNewChildren()
282     {
283         auto result = construct!IstNode;
284         addChild(result);
285         return result;
286     }
287 
288     /**
289      * Sets the infomations describing the property associated
290      * to this IST node.
291      */
292     void setDescriptor(T)(PropDescriptor!T* descriptor)
293     {
294         if (descriptor)
295             setNodeInfo!T(&_info, descriptor);
296     }
297 
298     /**
299      * Returns a pointer to the information describing the property
300      * associated to this IST node.
301      */
302     SerNodeInfo* info()
303     {
304         return &_info;
305     }
306 
307     /**
308      * Returns the parents identifier chain.
309      */
310     string parentIdentifiersChain()
311     {
312         if (!level)
313             return "";
314 
315         string[] items;
316         IstNode curr = cast(IstNode) parent;
317         while (curr)
318         {
319             items ~= curr.info.name[];
320             curr = cast(IstNode) curr.parent;
321         }
322         return items.retro.join(".");
323     }
324 
325     /**
326      * Returns the identifier chain.
327      */
328     string identifiersChain()
329     {
330         if (!level)
331             return info.name[].idup;
332         else
333             return parentIdentifiersChain ~ "." ~ info.name[].idup;
334     }
335 }
336 //----
337 
338 // Value as text to ubyte[] converters ----------------------------------------+
339 
340 private static char[] invalidText = "invalid".dup;
341 
342 /// Converts the raw data contained in a SerNodeInfo to its string representation.
343 char[] value2text(SerNodeInfo* nodeInfo)
344 {
345     char[] v2t_1(T)(){return to!string(*cast(T*)nodeInfo.value.ptr).dup;}
346     char[] v2t_2(T)(){return to!string(cast(T[])nodeInfo.value[]).dup;}
347     char[] v2t(T)(){if (!nodeInfo.rtti.dimension) return v2t_1!T; else return v2t_2!T;}
348     //
349     with (RtType) final switch(nodeInfo.rtti.type)
350     {
351         case _invalid, _aa, _pointer, _union: return invalidText;
352         case _bool:     return v2t!bool;
353         case _ubyte:    return v2t!ubyte;
354         case _byte:     return v2t!byte;
355         case _ushort:   return v2t!ushort;
356         case _short:    return v2t!short;
357         case _uint:     return v2t!uint;
358         case _int:      return v2t!int;
359         case _ulong:    return v2t!ulong;
360         case _long:     return v2t!long;
361         case _float:    return v2t!float;
362         case _double:   return v2t!double;
363         case _real:     return v2t!real;
364         case _char:     return v2t!char;
365         case _wchar:    return v2t!wchar;
366         case _dchar:    return v2t!dchar;
367         case _enum:     return v2t_2!char;
368         case _object:   return cast(char[]) nodeInfo.value;
369         case _stream:   return to!(char[])(nodeInfo.value[]);
370         case _funptr:   return v2t_2!char;
371         case _struct:
372             if (nodeInfo.rtti.structInfo.type == StructType._binary)
373                 return v2t_2!ubyte;
374             else
375                 return cast(char[]) nodeInfo.value;
376     }
377 }
378 
379 /// Converts the literal representation to a ubyte array according to type.
380 ubyte[] text2value(char[] text, const SerNodeInfo* nodeInfo)
381 {
382     ubyte[] t2v_1(T)()
383     {
384         auto res = new ubyte[](T.sizeof);
385         *cast(T*) res.ptr = to!T(text);
386         return res;
387     }
388     ubyte[] t2v_2(T)()
389     {
390         auto v = to!(T[])(text);
391         auto res = new ubyte[](v.length * T.sizeof);
392         moveMem(res.ptr, v.ptr, res.length);
393         return res;
394     }
395     ubyte[] t2v(T)(){
396         if (!nodeInfo.rtti.dimension) return t2v_1!T; else return t2v_2!T;
397     }
398     //
399     with(RtType) final switch(nodeInfo.rtti.type)
400     {
401         case _invalid, _aa, _pointer, _union: return cast(ubyte[])invalidText;
402         case _bool:     return t2v!bool;
403         case _ubyte:    return t2v!ubyte;
404         case _byte:     return t2v!byte;
405         case _ushort:   return t2v!ushort;
406         case _short:    return t2v!short;
407         case _uint:     return t2v!uint;
408         case _int:      return t2v!int;
409         case _ulong:    return t2v!ulong;
410         case _long:     return t2v!long;
411         case _float:    return t2v!float;
412         case _double:   return t2v!double;
413         case _real:     return t2v!real;
414         case _char:     return t2v!char;
415         case _wchar:    return t2v_2!wchar;
416         case _dchar:    return t2v!dchar;
417         case _enum:     return cast(ubyte[]) text;
418         case _object:   return cast(ubyte[]) text;
419         case _stream:   return t2v_2!ubyte;
420         case _funptr:   return cast(ubyte[]) text;
421         case _struct:
422             if (nodeInfo.rtti.structInfo.type == StructType._binary)
423                 return t2v_2!ubyte;
424             else
425                 return cast(ubyte[]) text;
426     }
427 }
428 //----
429 
430 // Descriptor to node & node to descriptor ------------------------------------+
431 
432 /// Restores the raw value contained in a SerNodeInfo using the associated setter.
433 void nodeInfo2Declarator(SerNodeInfo* nodeInfo)
434 {
435     void toDecl1(T)()  {
436         auto descr = cast(PropDescriptor!T *) nodeInfo.descriptor;
437         descr.set( *cast(T*) nodeInfo.value.ptr );
438     }
439     void toDecl2(T)() {
440         auto descr = cast(PropDescriptor!(T[]) *) nodeInfo.descriptor;
441         descr.set(cast(T[]) nodeInfo.value[]);
442     }
443     void toDecl(T)() {
444         (!nodeInfo.rtti.dimension) ? toDecl1!T : toDecl2!T;
445     }
446     //
447     with (RtType) final switch(nodeInfo.rtti.type)
448     {
449         case _invalid, _aa, _pointer, _union:  break;
450         case _bool:     toDecl!bool; break;
451         case _byte:     toDecl!byte; break;
452         case _ubyte:    toDecl!ubyte; break;
453         case _short:    toDecl!short; break;
454         case _ushort:   toDecl!ushort; break;
455         case _int:      toDecl!int; break;
456         case _uint:     toDecl!uint; break;
457         case _long:     toDecl!long; break;
458         case _ulong:    toDecl!ulong; break;
459         case _float:    toDecl!float; break;
460         case _double:   toDecl!double; break;
461         case _real:     toDecl!real; break;
462         case _char:     toDecl!char; break;
463         case _wchar:    toDecl!wchar; break;
464         case _dchar:    toDecl!dchar; break;
465         case _enum:
466             import std.algorithm.searching;
467             int i = cast(int) countUntil(nodeInfo.rtti.enumInfo.members, cast(string) nodeInfo.value);
468             assert(i > -1);
469             auto descr = cast(PropDescriptor!int *) nodeInfo.descriptor;
470             descr.set(nodeInfo.rtti.enumInfo.values[i]);
471             break;
472         case _object:   break;
473         case _struct:
474             final switch(nodeInfo.rtti.structInfo.type)
475             {
476                 case StructType._none: assert(0);
477                 case StructType._text:
478 
479                     void* structPtr = (cast(PropDescriptor!GenericStruct*) nodeInfo.descriptor).getter()().getThis;
480                     void* oldCtxt = nodeInfo.rtti.structInfo.textTraits.setContext(structPtr);
481                     assert(nodeInfo.rtti.structInfo.textTraits.loadFromText.ptr);
482                     nodeInfo.rtti.structInfo.textTraits.loadFromText(cast(const(char)[]) nodeInfo.value);
483                     nodeInfo.rtti.structInfo.textTraits.restoreContext(oldCtxt);
484                     break;
485                 case StructType._binary:
486                     void* structPtr = (cast(PropDescriptor!GenericStruct*) nodeInfo.descriptor).getter()().getThis;
487                     void* oldCtxt = nodeInfo.rtti.structInfo.binTraits.setContext(structPtr);
488                     assert(nodeInfo.rtti.structInfo.binTraits.loadFromBytes.ptr);
489                     nodeInfo.rtti.structInfo.binTraits.loadFromBytes(cast(ubyte[])nodeInfo.value);
490                     nodeInfo.rtti.structInfo.binTraits.restoreContext(oldCtxt);
491                     break;
492                 case StructType._publisher:
493                     break;
494             }
495             break;
496         case _stream:
497             MemoryStream str = construct!MemoryStream;
498             scope(exit) destruct(str);
499             str.write(cast(ubyte*)nodeInfo.value.ptr, nodeInfo.value.length);
500             str.position = 0;
501             auto descr = cast(PropDescriptor!Stream*) nodeInfo.descriptor;
502             descr.set(str);
503             break;
504         case _funptr:
505             char[] refId = cast(char[]) nodeInfo.value[];
506             bool setFromGenericRef(T)()
507             {
508                 bool result;
509                 if (T* funOrDg = ReferenceMan.reference!(T)(refId))
510                 {
511                     auto descr = cast(PropDescriptor!T*) nodeInfo.descriptor;
512                     descr.set(*funOrDg);
513                     result = true;
514                 }
515                 return result;
516             }
517 
518             bool setFuncFromTypeIdentifier()
519             {
520                 bool result;
521                 if (void* fod = ReferenceMan.reference(nodeInfo.rtti.funptrInfo.identifier, refId))
522                 {
523                     result =  true;
524                     auto descr = cast(PropDescriptor!GenericFunction*) nodeInfo.descriptor;
525                     descr.set(cast(GenericFunction)fod);
526                 }
527                 return result;
528             }
529 
530             bool setDgFromTypeIdentifier()
531             {
532                 bool result;
533                 if (void* fod = ReferenceMan.reference(nodeInfo.rtti.funptrInfo.identifier, refId))
534                 {
535                     result = true;
536                     auto descr = cast(PropDescriptor!GenericDelegate*) nodeInfo.descriptor;
537                     descr.set(*cast(GenericDelegate*)fod);
538                 }
539                 return result;
540             }
541 
542             if (nodeInfo.rtti.funptrInfo.hasContext)
543             {
544                 if (!setDgFromTypeIdentifier)
545                     setFromGenericRef!GenericDelegate;
546             }
547             else
548             {
549                 if (!setFuncFromTypeIdentifier)
550                     setFromGenericRef!GenericFunction;
551             }
552     }
553 }
554 
555 /// Fills an SerNodeInfo according to an PropDescriptor
556 void setNodeInfo(T)(SerNodeInfo* nodeInfo, PropDescriptor!T* descriptor)
557 {
558     scope(failure) nodeInfo.isDamaged = true;
559 
560     nodeInfo.rtti = descriptor.rtti;
561     nodeInfo.descriptor = cast(Ptr) descriptor;
562     nodeInfo.name = descriptor.name[];
563 
564     // simple, fixed-length (or convertible to), types
565     static if (isBasicRtType!T)
566     {
567         nodeInfo.value.length = nodeInfo.rtti.type.size;
568         *cast(T*) nodeInfo.value.ptr = descriptor.get();
569     }
570 
571     // arrays types
572     else static if (isSerArrayType!T)
573     {
574         T value = descriptor.get();
575         nodeInfo.value.length = value.length * nodeInfo.rtti.type.size;
576         moveMem(nodeInfo.value.ptr, cast(void*) value.ptr, nodeInfo.value.length);
577     }
578 
579     // enums
580     else static if (is(T == GenericEnum))
581     {
582         uint v = (*descriptor.as!uint).get();
583         switch(nodeInfo.rtti.enumInfo.valueType.type.size)
584         {
585             case 1: v &= 0xFF; break;
586             case 2: v &= 0xFFFF; break;
587             default:
588         }
589         import std.algorithm.searching: countUntil;
590         ptrdiff_t i = countUntil(nodeInfo.rtti.enumInfo.values, v);
591         assert (i != -1);
592         nodeInfo.value = cast(ubyte[]) nodeInfo.rtti.enumInfo.members[i];
593     }
594 
595     // Serializable or Object
596     else static if (isSerObjectType!T)
597     {
598         Object obj = cast(Object) descriptor.get();
599         if (obj)
600             nodeInfo.value = cast(ubyte[]) className(obj);
601     }
602 
603     // struct, warning, T is set to a dummy struct type
604     else static if (is(T == struct) || is(T==GenericStruct))
605     {
606         const(Rtti)* ti = nodeInfo.rtti;
607         final switch(ti.structInfo.type)
608         {
609             case StructType._none, StructType._publisher:
610                 nodeInfo.value = cast(ubyte[]) ti.structInfo.identifier;
611                 break;
612             case StructType._text:
613                 void* structPtr = descriptor.getter()().getThis;
614                 void* oldCtxt = ti.structInfo.textTraits.setContext(structPtr);
615                 assert(ti.structInfo.textTraits.saveToText.ptr);
616                 nodeInfo.value = cast(ubyte[]) ti.structInfo.textTraits.saveToText();
617                 ti.structInfo.textTraits.restoreContext(oldCtxt);
618                 break;
619             case StructType._binary:
620                 void* structPtr = descriptor.getter()().getThis;
621                 void* oldCtxt = ti.structInfo.binTraits.setContext(structPtr);
622                 assert(ti.structInfo.binTraits.saveToBytes.ptr);
623                 nodeInfo.value = ti.structInfo.binTraits.saveToBytes();
624                 ti.structInfo.binTraits.restoreContext(oldCtxt);
625         }
626     }
627 
628     // stream
629     else static if (is(T : Stream))
630     {
631         Stream value = descriptor.get();
632         value.position = 0;
633         static if (is(T == MemoryStream))
634         {
635             nodeInfo.value = (cast(MemoryStream)value).ubytes;
636         }
637         else
638         {
639             nodeInfo.value.length = cast(uint) value.size;
640             value.read(nodeInfo.value.ptr, cast(uint) value.size);
641         }
642     }
643 
644     // delegate & function
645     else static if (is(T == GenericDelegate) || is(T == GenericFunction))
646     {
647         auto dg = descriptor.get;
648         const(char)[] id;
649         id = ReferenceMan.referenceID(&dg);
650         if (!id.length) id = ReferenceMan.referenceID!(is(T == GenericDelegate))
651             (nodeInfo.rtti.funptrInfo.identifier, &dg);
652         nodeInfo.value = cast(ubyte[]) id;
653     }
654 
655     else static assert(0, "cannot set the ISTnode for a " ~ T.stringof);
656 }
657 //----
658 
659 // Serialization formats ------------------------------------------------------+
660 
661 /// Enumerates the possible serialization format
662 enum SerializationFormat : ubyte
663 {
664     /// native binary format
665     izbin,
666     /// native text format
667     iztxt,
668     /// JSON chunks
669     json
670 }
671 
672 /// Enumerates the possible token handled by a serialization format
673 enum FormatToken
674 {
675     /// Nothing was read.
676     unknow,
677     ///
678     beg,
679     /// An object is read or written.
680     objBeg,
681     /// An object has been object read or written.
682     objEnd,
683     /// A property is read or written.
684     prop,
685     /// Nothing to read anymore.
686     end,
687 }
688 
689 /// Prototype of a function that writes an IstNode representation to a Stream.
690 alias FormatWriter = void function(IstNode istNode, Stream stream, const FormatToken tok);
691 
692 /// Prototype of a function that reads an IstNode representation from a Stream.
693 alias FormatReader = FormatToken function(Stream stream, IstNode istNode);
694 
695 // JSON format ----------------------------------------------------------------+
696 private void writeJSON(IstNode istNode, Stream stream, const FormatToken tok)
697 {
698     import std.json: JSONValue, toJSON;
699     version(assert) const bool pretty = true; else const bool pretty = false;
700     //
701     switch (tok)
702     {
703         case FormatToken.beg:
704             //stream.writeChar('{');
705             return;
706         case FormatToken.end:
707             //stream.writeChar('}');
708             return;
709         case FormatToken.objEnd:
710             foreach(i; 0 .. istNode.level) stream.writeChar('\t');
711             if (pretty)
712             {
713                 stream.position = stream.position - 2;
714                 stream.writeChar(' ');
715                 stream.writeChar(' ');
716             }
717             else
718             {
719                 stream.position = stream.position - 1;
720                 stream.writeChar(' ');
721             }
722             stream.writeChar(']');
723             stream.writeChar('}');
724             stream.writeChar(',');
725             stream.writeChar('\n');
726             return;
727         default:
728     }
729     //
730     auto level  = JSONValue(istNode.level);
731     auto type   = JSONValue(typeString(istNode.info.rtti));
732     auto name   = JSONValue(istNode.info.name[].idup);
733 
734     char[] txt;
735     if (tok == FormatToken.objBeg)
736     {
737         auto prop   = JSONValue(["type": type, "name": name]);
738         txt    = (toJSON(prop, pretty)[0..$-1] ~ ",\"value\" : [").dup;
739     }
740     else
741     {
742         auto value  = JSONValue(value2text(istNode.info).idup);
743         auto prop   = JSONValue(["type": type, "name": name, "value": value]);
744         txt = (toJSON(prop, pretty) ~ ",").dup;
745     }
746 
747 
748 
749     //
750     stream.write(txt.ptr, txt.length);
751 }
752 
753 private FormatToken readJSON(Stream stream, IstNode istNode)
754 {
755     import std.json: JSONValue, parseJSON, JSON_TYPE;
756     // cache property
757     size_t cnt, len;
758     char c;
759     bool skip;
760     auto immutable stored = stream.position;
761     while (true)
762     {
763         if (stream.position == stream.size)
764             return FormatToken.end;
765         ++len;
766         stream.read(&c, 1);
767         if (c == '\\')
768             continue;
769         if (c == '"')
770             skip = !skip;
771         if (!skip)
772         {
773             cnt += (c == '{');
774             cnt -= (c == '}');
775         }
776         if (cnt == 0)
777             break;
778     }
779     stream.position = stored;
780     char[] cache;
781     cache.length = len;
782     stream.read(cache.ptr, cache.length);
783     //
784     const JSONValue prop = parseJSON(cache);
785 
786     const(JSONValue)* type = "type" in prop;
787     if (type && type.type == JSON_TYPE.STRING)
788         istNode.info.rtti = getRtti(type.str);
789     else
790         istNode.info.isDamaged = true;
791 
792     const(JSONValue)* name = "name" in prop;
793     if (name && name.type == JSON_TYPE.STRING)
794         istNode.info.name = name.str.dup;
795     else
796         istNode.info.isDamaged = true;
797 
798     const(JSONValue)* value = "value" in prop;
799     if (value && value.type == JSON_TYPE.STRING)
800         istNode.info.value = text2value(value.str.dup, istNode.info);
801     else
802         istNode.info.isDamaged = true;
803 
804     return FormatToken.prop;
805 }
806 // ----
807 
808 // Text format ----------------------------------------------------------------+
809 private void writeText(IstNode istNode, Stream stream, const FormatToken tok)
810 {
811     switch (tok)
812     {
813         case FormatToken.beg, FormatToken.end:
814             return;
815         case FormatToken.objEnd:
816             foreach(i; 0 .. istNode.level) stream.writeChar('\t');
817             stream.writeChar('}');
818             stream.writeChar('\n');
819             return;
820         default:
821     }
822     // indentation
823     foreach(i; 0 .. istNode.level) stream.writeChar('\t');
824     // type
825     char[] type = typeString(istNode.info.rtti).dup;
826     type = replace(type, " ", "-");
827     stream.write(type.ptr, type.length);
828     stream.writeChar(' ');
829     // name
830     char[] name = istNode.info.name[];
831     stream.write(name.ptr, name.length);
832     // name value separators
833     char[] name_value = " = \"".dup;
834     stream.write(name_value.ptr, name_value.length);
835     // value
836     char[] value = value2text(istNode.info);
837     with (RtType) if (istNode.info.rtti.type >= _char &&
838         istNode.info.rtti.type <= _dchar && istNode.info.rtti.dimension > 0)
839     {
840         value = escape(value, [['\n','n'],['"','"']]);
841     }
842     stream.write(value.ptr, value.length);
843     char[] eol = "\"\n".dup;
844     stream.write(eol.ptr, eol.length);
845     if (tok == FormatToken.objBeg)
846     {
847         foreach(i; 0 .. istNode.level) stream.writeChar('\t');
848         value = "{\n".dup;
849         stream.write(value.ptr, value.length);
850     }
851 }
852 
853 private FormatToken readText(Stream stream, IstNode istNode)
854 {
855     char[] text, identifier;
856     char curr, old;
857 
858     // cache property
859     while (true)
860     {
861         if (stream.position == stream.size)
862             return FormatToken.end;
863 
864         old = curr;
865         curr = stream.readChar;
866         if (curr == '\n' && old == '}')
867             return FormatToken.objEnd;
868         if (curr == '"' && old == '\\')
869         {
870             text ~= curr;
871             continue;
872         }
873         if (curr == '\n' && old == '"')
874             break;
875         else if (curr == '"' && stream.position == stream.size)
876             break;
877         text ~= curr;
878     }
879 
880     // skip indentation
881     skipWord(text, whiteChars);
882     // type & name;
883     identifier = nextWord(text);
884     identifier = replace(identifier, "-", " ");
885     istNode.info.rtti = getRtti(identifier);
886     assert(istNode.info.rtti, identifier);
887     // name
888     istNode.info.name = nextWord(text).idup;
889     // name value separator
890     identifier = nextWord(text);
891     if (identifier != "=") istNode.info.isDamaged = true;
892     // value
893     skipWordUntil(text, '"');
894     identifier = text[1..$-1];
895     with (RtType) if (istNode.info.rtti.type >= _char &&
896         istNode.info.rtti.type <= _dchar && istNode.info.rtti.dimension > 0)
897     {
898         identifier = unEscape(identifier, [['\n','n'],['"','"']]);
899     }
900     istNode.info.value = text2value(identifier, istNode.info);
901 
902     // object begins ?
903     auto savedPos = stream.position;
904     while (true)
905     {
906         old = curr;
907         curr = stream.readChar;
908         if (curr == '\n' && old == '{')
909             return FormatToken.objBeg;
910         else if (curr == '\n')
911         {
912             stream.position = savedPos;
913             break;
914         }
915     }
916     return FormatToken.prop;
917 }
918 //----
919 
920 // Binary format --------------------------------------------------------------+
921 version(BigEndian) private ubyte[] swapBE(const ref ubyte[] input, size_t div)
922 {
923     if (div == 1) return input.dup;
924     auto result = new ubyte[](input.length);
925     switch(div) {
926         default: break;
927         case 2: foreach(immutable i; 0 .. input.length / div) {
928             result[i*2+0] = input[i*2+1];
929             result[i*2+1] = input[i*2+0];
930         } break;
931         case 4: foreach(immutable i; 0 .. input.length / div) {
932             result[i*4+0] = input[i*4+3];
933             result[i*4+1] = input[i*4+2];
934             result[i*4+2] = input[i*4+1];
935             result[i*4+3] = input[i*4+0];
936         } break;
937         case 8: foreach(immutable i; 0 .. input.length / div) {
938             result[i*8+0] = input[i*8+7];
939             result[i*8+1] = input[i*8+6];
940             result[i*8+2] = input[i*8+5];
941             result[i*8+3] = input[i*8+4];
942             result[i*8+4] = input[i*8+3];
943             result[i*8+5] = input[i*8+2];
944             result[i*8+6] = input[i*8+1];
945             result[i*8+7] = input[i*8+0];
946         } break;
947     }
948     return result;
949 }
950 
951 private void writeBin(IstNode istNode, Stream stream, const FormatToken tok)
952 {
953     switch (tok)
954     {
955         case FormatToken.beg, FormatToken.end:
956             return;
957         case FormatToken.objEnd:
958             stream.writeUbyte(0xFE);
959             return;
960         default:
961     }
962 
963     ubyte[] data;
964     uint datalength;
965     // total size
966     auto sizePos = stream.position;
967     stream.writeUint(0);
968     // header
969     stream.writeUbyte(0x99);
970     // type
971     stream.writeUbyte(cast(ubyte) istNode.info.rtti.type);
972     // as array
973     stream.writeBool(istNode.info.rtti.dimension > 0);
974     // opt type identifier stringz
975     if (istNode.info.rtti.type == RtType._enum ||
976         istNode.info.rtti.type == RtType._struct ||
977         istNode.info.rtti.type == RtType._funptr ||
978         istNode.info.rtti.type == RtType._object)
979     {
980         data = cast(ubyte[]) istNode.info.rtti.enumInfo.identifier;
981         stream.write(data.ptr, data.length);
982     }
983     stream.writeUbyte(0x0);
984     // namez
985     data = cast(ubyte[]) istNode.info.name[];
986     stream.write(data.ptr, data.length);
987     stream.writeUbyte(0);
988     // value length then value
989     version(LittleEndian)
990         data = istNode.info.value;
991     else
992         data = swapBE(istNode.info.value, type2size[istNode.info.type]);
993 
994     datalength = cast(uint) data.length;
995     stream.writeUint(cast(uint) datalength);
996     stream.write(data.ptr, data.length);
997 
998     // sub obj
999     stream.writeBool(tok == FormatToken.objBeg);
1000     //footer
1001     stream.writeUbyte(0xA0);
1002     // size
1003     auto savedEnd = stream.position;
1004     stream.position = sizePos;
1005     stream.writeUint(cast(uint) (savedEnd - sizePos));
1006     stream.position = savedEnd;
1007 }
1008 
1009 private FormatToken readBin(Stream stream, IstNode istNode)
1010 {
1011     ubyte bin;
1012     ubyte[] data;
1013     uint datalength;
1014     uint sze;
1015     import std..string: fromStringz;
1016     //
1017     if (stream.position == stream.size)
1018         return FormatToken.end;
1019     bin = stream.readUbyte;
1020     if (bin == 0xFE)
1021         return FormatToken.objEnd;
1022     stream.position = stream.position-1;
1023     // cache property
1024     sze = stream.readUint;
1025     data.length = sze - 4;
1026     stream.read(data.ptr, data.length);
1027     //
1028     assert(data[0] == 0x99);
1029     assert(data[$-1] == 0xA0);
1030     FormatToken result = (data[$-2] == 1) ? FormatToken.objBeg : FormatToken.prop;
1031     // type and array
1032     string tstr;
1033     uint offs = 3;
1034     if (data[1] == RtType._enum || data[1] == RtType._struct ||
1035         data[1] == RtType._funptr || data[1] == RtType._object)
1036     {
1037         tstr = fromStringz( &(cast(string)data)[3] );
1038         offs += tstr.length;
1039     }
1040     else tstr = typeString(cast(RtType) data[1]);
1041     offs += 1;
1042     if (data[2]) tstr ~= "[]";
1043     istNode.info.rtti = getRtti(tstr);
1044     assert(istNode.info.rtti, `"` ~ tstr ~ `"` );
1045     // namez
1046     string name = fromStringz(&(cast(string)data)[offs]);
1047     offs += (name.length + 1);
1048     istNode.info.name = name;
1049     // value length then value
1050     version(LittleEndian)
1051     {
1052         datalength = *cast(uint*) (data.ptr + offs);
1053         istNode.info.value = data[offs + 4 .. offs + 4 + datalength];
1054     }
1055     else
1056     {
1057         datalength = *cast(uint*) (data.ptr + offs);
1058         data = data[offs + 4 .. offs + 4 + datalength];
1059         istNode.info.value = swapBE(data, istNode.info.type.size);
1060     }
1061     return result;
1062 }
1063 //----
1064 
1065 /// The serialization format used when not specified.
1066 alias defaultFormat = SerializationFormat.iztxt;
1067 
1068 private FormatWriter writeFormat(SerializationFormat format)
1069 {
1070     with(SerializationFormat) final switch(format)
1071     {
1072         case izbin: return &writeBin;
1073         case iztxt: return &writeText;
1074         case json:  return &writeJSON;
1075     }
1076 }
1077 
1078 private FormatReader readFormat(SerializationFormat format)
1079 {
1080     with(SerializationFormat) final switch(format)
1081     {
1082         case izbin: return &readBin;
1083         case iztxt: return &readText;
1084         case json:  return &readJSON;
1085     }
1086 }
1087 //----
1088 
1089 // Main Serializer class ------------------------------------------------------+
1090 
1091 /**
1092  * Prototype of the event triggered when a serializer misses a property descriptor.
1093  * Params:
1094  *      node = The information used to determine the descriptor to return.
1095  *      descriptor = What the serializer want. If set to null then node is not restored.
1096  *      stop = the callee can set this value to true to stop the restoration
1097  *      process. According to the serialization context, this value can be noop.
1098  */
1099 alias WantDescriptorEvent = void delegate(IstNode node, ref Ptr descriptor, out bool stop);
1100 
1101 
1102 /**
1103  * Prototype of the event called when a serializer fails to get an aggregate to
1104  * deserialize.
1105  *
1106  * Params:
1107  *      node = The information the callee uses to set the undefined aggregate.
1108  *      aggregate = The object or a pointer to the struct where the deserialization
1109  *          continues.
1110  *      fromReference = When set to true, the serializer tries to find the aggregate
1111  *          in the ReferenceMan.
1112  */
1113 alias WantAggregateEvent = void delegate(IstNode node, ref void* aggregate, out bool fromRefererence);
1114 
1115 
1116 //TODO-cserializer: error handling (using isDamaged + errors when reading a format).
1117 //TODO-cserializer: convert FP to string using format("%.{9|17}g",value).
1118 
1119 /**
1120  * The Serializer class is specialized to store and restore the members of
1121  * an Object.
1122  *
1123  * PropertyPublisher:
1124  * A Serializer serializes trees of classes and struct that implement the
1125  * PropertyPublisher interface. Their publications define what is saved or
1126  * restored. Object descriptors leading to an owned Object define the structure.
1127  * Basics types and array of basic types are handled. Special cases exist to
1128  * manage Stream properties, delegates or objects that are stored in the ReferenceMan.
1129  * It's even possible to handle more complex types by using or writing custom
1130  * PropertyPublishers, such as those defined in iz.classes.
1131  *
1132  * Ownership:
1133  * Sub objects can be fully serialized or not. This is determined by the ownership.
1134  * A sub object is considered as owned when its member 'declarator' matches to
1135  * to the member 'declarator' of the descriptor that returns this sub object.
1136  * When not owned, the sub object publications are not stored, instead, the
1137  * serializer writes its unique identifier, as found in the ReferenceMan.
1138  * When deserializing, the opposite process happens: the serializer tries to
1139  * restore the reference using the ReferenceMan. Ownership is automatically set
1140  * by the $(D PropertyPubliserImpl) analyzers.
1141  *
1142  * Representation:
1143  * The serializer uses an intermediate serialization tree (IST) that ensures a
1144  * certain flexibilty against a traditional single-shot sequential serialization.
1145  * As expected for a serializer, object trees can be stored or restored by
1146  * a simple and single call to $(D publisherToStream()) in pair with
1147  * $(D streamToPublisher()) but the IST also allows to convert a Stream or
1148  * to find and restores a specific property.
1149  *
1150  * Errors:
1151  * Two events ($(D onWantDescriptor) and  $(D onWantAggregate)) allow to handle
1152  * the errors that could be encountered when restoring.
1153  * They permit a PropertyPublisher to be modified without any risk of deserialization
1154  * failure. Data saved from an older version can be recovered by deserializing in
1155  * a temporary property and converted to a new type. They can also be skipped,
1156  * without stopping the whole processing. Missing objects can be created when
1157  * The serializer ask for, since in this case, the original Object type and the
1158  * original variable names are passed as hint.
1159  *
1160  * Hints:
1161  * The serializer handles the $(D PropHints) of the publications. If $(D PropHint.dontSet)
1162  * is in the hints then a property is not restored. If $(D PropHint.dontGet) is in the
1163  * hints then the property is not stored. These two hints allow to deprecate some
1164  * publications, without breaking the restoration. The hint $(D PropHint.initCare)
1165  * indicates that a property equal to its initializer is written. For floating point
1166  * types there is an exception that is the intializer is considered to be 0.
1167  */
1168 class Serializer
1169 {
1170 
1171 private:
1172 
1173     IstNodeCache _nodesCache;
1174     /// the IST root
1175     IstNode _rootNode;
1176     /// the current parent node, always represents a PropertyPublisher
1177     IstNode _parentNode;
1178     /// the last created node
1179     IstNode _previousNode;
1180     /// the PropertyPublisher linked to _rootNode
1181     Object  _rootPublisher;
1182 
1183     Object  _declarator;
1184 
1185     WantAggregateEvent _onWantAggregate;
1186     WantDescriptorEvent _onWantDescriptor;
1187     void delegate(void*) _onFinishAggregate;
1188 
1189     SerializationFormat _format;
1190 
1191     Stream _stream;
1192     PropDescriptor!Object _rootDescr;
1193 
1194     bool _mustWrite;
1195     bool _mustRead;
1196 
1197     void addIstNodeForDescriptor(T)(PropDescriptor!T * descriptor)
1198     if (isSerializable!T && !isSerObjectType!T)
1199     in
1200     {
1201         assert(descriptor);
1202         assert(descriptor.name.length);
1203     }
1204     body
1205     {
1206         if (PropHint.dontGet in descriptor.hints)
1207             return;
1208         if (PropHint.initCare !in descriptor.hints)
1209         {
1210             static if (isNumeric!T)
1211             {
1212                 if (descriptor.get() == 0)
1213                     return;
1214             }
1215             else static if (is(T == bool))
1216             {
1217                 if (descriptor.get() == false)
1218                     return;
1219             }
1220             else static if (is(T == GenericEnum))
1221             {
1222                 //if (descriptor.get() == 0)
1223                 //    return;
1224             }
1225             else static if (isArray!T)
1226             {
1227                 if (!descriptor.get().length)
1228                     return;
1229             }
1230             else static if (is(T == GenericFunction) ||is(T == GenericDelegate))
1231             {
1232                 auto dg = descriptor.get();
1233                 if (dg == null)
1234                     return;
1235                 const(char)[] id = ReferenceMan.referenceID(&dg);
1236                 if (!id.length) id = ReferenceMan.referenceID!(is(T == GenericDelegate))
1237                     (descriptor.rtti.funptrInfo.identifier, &dg);
1238                 if (id.length == 0)
1239                     return;
1240             }
1241         }
1242         IstNode propNode = _parentNode.addNewChildren;
1243         propNode.setDescriptor(descriptor);
1244 
1245         if (_mustWrite)
1246         {
1247             const bool isPub = isPublisingStruct(descriptor.rtti) || descriptor.rtti.type == RtType._object;
1248             if (isPub) writeFormat(_format)(propNode, _stream, FormatToken.objBeg);
1249             else writeFormat(_format)(propNode, _stream, FormatToken.prop);
1250             if (isPub) writeFormat(_format)(propNode, _stream, FormatToken.objEnd);
1251         }
1252 
1253         _previousNode = propNode;
1254     }
1255 
1256     bool restoreFromEvent(IstNode node, out bool stop)
1257     {
1258         if (!_onWantDescriptor)
1259             return false;
1260         _onWantDescriptor(node, node.info.descriptor, stop);
1261         if (node.info.descriptor)
1262         {
1263             nodeInfo2Declarator(node.info);
1264             return true;
1265         }
1266         else if (isSerObjectType(node.info.rtti.type))
1267             return true;
1268         else
1269             return false;
1270     }
1271 
1272     bool descriptorMatchesNode(T)(PropDescriptor!T* descr, IstNode node)
1273     if (isSerializable!T)
1274     {
1275         if (!descr || !node.info.name.length || descr.name != node.info.name ||
1276             getRtti!T !is descr.rtti) return false;
1277         else
1278             return true;
1279     }
1280 
1281     void addPropertyPublisher(PD)(PD* pubDescriptor)
1282     {
1283         assert(pubDescriptor);
1284 
1285         if (PropHint.dontGet in pubDescriptor.hints)
1286             return;
1287 
1288         static if (is(PD == PropDescriptor!Object))
1289         {
1290             PropertyPublisher publisher;
1291             publisher = cast(PropertyPublisher) pubDescriptor.get();
1292             enum declIsClass = true;
1293         }
1294         else
1295         {
1296             const(Rtti)* prtti = pubDescriptor.rtti;
1297             if (prtti.type != RtType._struct)
1298                 return;
1299             if (prtti.structInfo.type != StructType._publisher)
1300                 return;
1301 
1302             const(PubTraits)* publisher = prtti.structInfo.pubTraits;
1303             enum declIsClass = false;
1304         }
1305 
1306         // write/Set object node
1307         if (!_parentNode) _parentNode = _rootNode;
1308         else _parentNode = _parentNode.addNewChildren;
1309         _parentNode.setDescriptor(pubDescriptor);
1310 
1311 
1312         if (/*_mustWrite*/false)
1313         {
1314             writeFormat(_format)(_parentNode, _stream, FormatToken.objBeg);
1315             writeFormat(_format)(_parentNode, _stream, FormatToken.prop);
1316             writeFormat(_format)(_parentNode, _stream, FormatToken.objEnd);
1317         }
1318 
1319         // reference: if not a PropPublisher
1320         if(!publisher)
1321             return;
1322 
1323         static if (declIsClass)
1324         {
1325             // reference: current publisher is not owned at all
1326             if (_parentNode !is _rootNode && publisher.declarator is null)
1327                 return;
1328 
1329             // reference: current publisher is not owned by the declarator
1330             if (_parentNode !is _rootNode && pubDescriptor.declarator !is publisher.declarator)
1331                 return;
1332         }
1333 
1334         // not a reference: current publisher is owned (it has initialized the target),
1335         // so write its members
1336         foreach(immutable i; 0 .. publisher.publicationCount())
1337         {
1338             auto descr = cast(GenericDescriptor*) publisher.publicationFromIndex(i);
1339             const(Rtti)* rtti = descr.rtti;
1340             //
1341             void addValueProp(T)()
1342             {
1343                 if (!rtti.dimension) addIstNodeForDescriptor(descr.as!T);
1344                 else addIstNodeForDescriptor(descr.as!(T[]));
1345             }
1346             with(RtType) final switch(rtti.type)
1347             {
1348                 case _invalid, _aa, _pointer, _union: assert(0);
1349                 case _bool:   addValueProp!bool; break;
1350                 case _byte:   addValueProp!byte; break;
1351                 case _ubyte:  addValueProp!ubyte; break;
1352                 case _short:  addValueProp!short; break;
1353                 case _ushort: addValueProp!ushort; break;
1354                 case _int:    addValueProp!int; break;
1355                 case _uint:   addValueProp!uint; break;
1356                 case _long:   addValueProp!long; break;
1357                 case _ulong:  addValueProp!ulong; break;
1358                 case _float:  addValueProp!float; break;
1359                 case _real:   addValueProp!real; break;
1360                 case _double: addValueProp!double; break;
1361                 case _char:   addValueProp!char; break;
1362                 case _wchar:  addValueProp!wchar; break;
1363                 case _dchar:  addValueProp!dchar; break;
1364                 case _enum:   addIstNodeForDescriptor(descr.as!GenericEnum); break;
1365                 case _object:
1366                     auto _oldParentNode = _parentNode;
1367                     addPropertyPublisher(descr.as!Object);
1368                     _parentNode = _oldParentNode;
1369                     break;
1370                 case _stream:
1371                     addIstNodeForDescriptor(descr.as!Stream);
1372                     break;
1373                 case _funptr:
1374                     if (rtti.funptrInfo.hasContext)
1375                         addIstNodeForDescriptor(descr.as!GenericDelegate);
1376                     else
1377                         addIstNodeForDescriptor(descr.as!GenericFunction);
1378                     break;
1379                 case _struct:
1380                     final switch (rtti.structInfo.type)
1381                     {
1382                         case StructType._none:
1383                             assert(0);
1384                         case StructType._publisher:
1385                             auto _oldParentNode = _parentNode;
1386                             PropDescriptor!GenericStruct* des = cast(PropDescriptor!GenericStruct*) descr;
1387                             void* structPtr = des.getter()().getThis;
1388                             void* oldCtxt = rtti.structInfo.pubTraits.setContext(structPtr);
1389                             addPropertyPublisher(des); // struct detected with rtti
1390                             rtti.structInfo.pubTraits.restoreContext(oldCtxt);
1391                             _parentNode = _oldParentNode;
1392                             break;
1393                         case StructType._text, StructType._binary:
1394                             addIstNodeForDescriptor(descr.as!GenericStruct);
1395                     }
1396             }
1397         }
1398     }
1399 
1400 public:
1401 
1402     ///
1403     this()
1404     {
1405         _rootNode = construct!IstNode;
1406     }
1407 
1408     ~this()
1409     {
1410         _rootNode.deleteChildren;
1411         destruct(_rootNode);
1412         destruct(_nodesCache);
1413     }
1414 
1415 //---- serialization ----------------------------------------------------------+
1416 
1417     /**
1418      * Saves the IST to a Stream.
1419      *
1420      * Params:
1421      *      outputStream = The stream where te data are written.
1422      *      format = The data format.
1423      */
1424     void istToStream(Stream outputStream, SerializationFormat format = defaultFormat)
1425     {
1426         _format = format;
1427         _stream = outputStream;
1428         _mustWrite = true;
1429         //
1430         writeFormat(_format)(null, _stream, FormatToken.beg);
1431         void writeNodesFrom(IstNode parent)
1432         {
1433             writeFormat(_format)(parent, _stream, FormatToken.objBeg);
1434             foreach(node; parent.children)
1435             {
1436                 auto child = cast(IstNode) node;
1437                 if (child.childrenCount)
1438                     writeNodesFrom(child);
1439                 else writeFormat(_format)(child, _stream, FormatToken.prop);
1440             }
1441             writeFormat(_format)(parent, _stream, FormatToken.objEnd);
1442         }
1443         writeNodesFrom(_rootNode);
1444         writeFormat(_format)(null, _stream, FormatToken.end);
1445         //
1446         _mustWrite = false;
1447         _stream = null;
1448     }
1449 
1450     /**
1451      * Builds the IST from a PropertyPublisher and stores each publication
1452      * of the publisher in a stream.
1453      *
1454      * Storage is performed just after a publication is detected.
1455      *
1456      * Params:
1457      *      root = Either a PropertyPublisher or an object that's been mixed
1458      *      with the PropertyPublisherImpl template.
1459      *      outputStream = The stream where the data are written.
1460      *      format = The serialized data format.
1461      */
1462     void publisherToStream(Object root, Stream outputStream,
1463         SerializationFormat format = defaultFormat)
1464     {
1465         _format = format;
1466         _stream = outputStream;
1467         //_mustWrite = true;
1468         _rootNode.deleteChildren;
1469         _previousNode = null;
1470         _parentNode = null;
1471         PropDescriptor!Object rootDescr = PropDescriptor!Object(&root, "root");
1472         addPropertyPublisher(&rootDescr);
1473         istToStream(outputStream, format);
1474         //_mustWrite = false;
1475         _stream = null;
1476     }
1477 
1478     /// ditto
1479     void publisherToStream(S)(ref S root, Stream outputStream,
1480         SerializationFormat format = defaultFormat)
1481     if (is(S == struct))
1482     {
1483         const(Rtti)* rtti = getRtti!S;
1484         if (rtti.structInfo.type != StructType._publisher)
1485             return;
1486         rtti.structInfo.pubTraits.setContext(&root);
1487 
1488         _format = format;
1489         _stream = outputStream;
1490         //_mustWrite = true;
1491         _rootNode.deleteChildren;
1492         _previousNode = null;
1493         _parentNode = null;
1494         PropDescriptor!S rootDescr = PropDescriptor!S(&root, "root");
1495         addPropertyPublisher(&rootDescr);
1496         istToStream(outputStream, format);
1497         //_mustWrite = false;
1498         _stream = null;
1499     }
1500 
1501     /**
1502      * Builds the IST from a PropertyPublisher.
1503      */
1504     void publisherToIst(Object root)
1505     {
1506         _mustWrite = false;
1507         _rootNode.deleteChildren;
1508         _previousNode = null;
1509         _parentNode = null;
1510         PropDescriptor!Object rootDescr = PropDescriptor!Object(cast(Object*)&root, "root");
1511         addPropertyPublisher(&rootDescr);
1512         _mustWrite = false;
1513         _stream = null;
1514     }
1515 
1516     /**
1517      * Builds the IST from a struct that has the traits of a property publisher.
1518      */
1519     void publisherToIst(S)(ref S root)
1520     if (is(S == struct))
1521     {
1522         const(Rtti)* rtti = getRtti!S;
1523         if (rtti.structInfo.type != StructType._publisher)
1524             return;
1525         rtti.structInfo.pubTraits.setContext(&root);
1526 
1527         _mustWrite = false;
1528         _rootNode.deleteChildren;
1529         _previousNode = null;
1530         _parentNode = null;
1531          PropDescriptor!S rootDescr = PropDescriptor!S(&root, "root");
1532         addPropertyPublisher(&rootDescr);
1533         _mustWrite = false;
1534         _stream = null;
1535     }
1536 
1537 
1538 //------------------------------------------------------------------------------
1539 //---- deserialization --------------------------------------------------------+
1540 
1541     /**
1542      * Restores the IST to a $(D PropertyPublisher).
1543      *
1544      * Can be called after $(D streamToIst), which builds the IST without defining
1545      * the $(D PropDescriptor) that matches to a node. The descriptors are
1546      * dynamically set using the publications of the root. If the procedure doesn't
1547      * detect the descriptor that matches to an IST node then the events
1548      * $(D onWantObject) and $(D onWantDescriptor) are called.
1549      *
1550      * Params:
1551      *      root = The object from where the restoration starts. It must be
1552      *      a $(D PropertyPublisher) or struct that contains $(D PropertyPublisherImpl).
1553      */
1554     void istToPublisher(R)(ref R root)
1555     if (is(R == class) || is(R == struct))
1556     {
1557         void restoreFrom(T)(IstNode node, ref T target)
1558         {
1559             static if (is(T : Object))
1560                 if (!target) return;
1561 
1562             foreach(child; node.children)
1563             {
1564                 IstNode childNode = cast(IstNode) child;
1565 
1566                 bool stop;
1567                 void* gdPtr = target.publicationFromName(childNode.info.name[]);
1568                 if (!gdPtr)
1569                 {
1570                     restoreFromEvent(childNode, stop);
1571                     if (stop)
1572                         return;
1573                     stop = false;
1574                     gdPtr = node.info.descriptor;
1575                 }
1576 
1577                 if (!gdPtr) continue;
1578 
1579                 GenericDescriptor* gd = cast(GenericDescriptor*) gdPtr;
1580                 if (PropHint.dontSet in gd.hints)
1581                     continue;
1582                 if (gd.rtti is childNode.info.rtti)
1583                 {
1584                     childNode.info.descriptor = gd;
1585                     nodeInfo2Declarator(childNode.info);
1586                     if (childNode.info.rtti.type == RtType._object)
1587                     {
1588                         auto od = cast(PropDescriptor!Object*) gd;
1589                         void* o = cast(void*) od.get();
1590                         bool fromRef;
1591 
1592                         if (!o && _onWantAggregate)
1593                             _onWantAggregate(childNode, o, fromRef);
1594 
1595                         if (fromRef || !o)
1596                         {
1597                             void* po = ReferenceMan.reference(
1598                                 childNode.info.rtti.classInfo.identifier,
1599                                 childNode.identifiersChain
1600                             );
1601                             if (po)
1602                                 od.set(cast(Object) po);
1603                         }
1604                         else
1605                         {
1606                             if (PropertyPublisher pub = cast(PropertyPublisher) cast(Object) o)
1607                                 restoreFrom(childNode, pub);
1608                         }
1609                     }
1610                     else if (childNode.info.rtti.type == RtType._struct &&
1611                         childNode.info.rtti.structInfo.type == StructType._publisher)
1612                     {
1613                         auto sd = cast(PropDescriptor!GenericStruct*) gd;
1614                         void* structPtr = sd.getter()().getThis;
1615                         bool fromRef;
1616 
1617                         if (!structPtr && _onWantAggregate)
1618                             _onWantAggregate(childNode, structPtr, fromRef);
1619 
1620                         if (fromRef || !structPtr)
1621                         {
1622                             void* ps = ReferenceMan.reference(
1623                                 childNode.info.rtti.classInfo.identifier,
1624                                 childNode.identifiersChain
1625                             );
1626                             if (ps)
1627                                 sd.set(*cast(GenericStruct*) ps);
1628                         }
1629                         else
1630                         {
1631                             void* oldCtxt = sd.rtti.structInfo.pubTraits.setContext(structPtr);
1632                             auto pubStruct = sd.rtti.structInfo.pubTraits;
1633                             restoreFrom(childNode, pubStruct);
1634                             sd.rtti.structInfo.pubTraits.restoreContext(oldCtxt);
1635                         }
1636                     }
1637                 }
1638                 else
1639                 {
1640                     restoreFromEvent(childNode, stop);
1641                     if (stop)
1642                         return;
1643                     stop = false;
1644                 }
1645             }
1646         }
1647         static if(is(R == class))
1648         {
1649             if (auto pub = cast(PropertyPublisher) root)
1650                 restoreFrom(_rootNode, pub);
1651             if (_onFinishAggregate)
1652                 _onFinishAggregate(&root);
1653         }
1654         else static if (is(R == struct))
1655         {
1656             const(Rtti)* rtti = getRtti!R;
1657             if (rtti.structInfo.type != StructType._publisher)
1658                 return;
1659             restoreFrom(_rootNode, root);
1660             if (_onFinishAggregate)
1661                 _onFinishAggregate(&root);
1662         }
1663     }
1664 
1665     /**
1666      * Builds the IST from a $(D Stream) and restores from root.
1667      *
1668      * This method calls successively $(D streamToIst()) then $(D istToPublisher()).
1669      *
1670      * Params:
1671      *      inputStream: The $(D Stream) that contains the data previously serialized.
1672      *      root = The object from where the restoration starts. It must be
1673      *      a $(D PropertyPublisher) or struct that contains $(D PropertyPublisherImpl).
1674      */
1675     void streamToPublisher(R)(Stream inputStream, ref R root,
1676         SerializationFormat format = defaultFormat)
1677     if (is(R == struct))
1678     {
1679         streamToIst(inputStream, format);
1680         istToPublisher(root);
1681     }
1682 
1683     /// ditto
1684     void streamToPublisher(R)(Stream inputStream, R root,
1685         SerializationFormat format = defaultFormat)
1686     if (is(R == class))
1687     {
1688         streamToIst(inputStream, format);
1689         istToPublisher(root);
1690     }
1691 
1692     /**
1693      * Builds the IST from a $(D Stream).
1694      *
1695      * After the call the IST nodes are not yet linked to their $(D PropDescriptor).
1696      * The deserialization process can be achieved manually, using $(D findNode())
1697      * in pair with $(D restoreProperty()) or automatically, using $(D istToPublisher()).
1698      * This function can also be used to convert from a format to another.
1699      *
1700      * Params:
1701      *      inputStream = The $(D Stream) containing the serialized data.
1702      *      format = The format of the serialized data.
1703      */
1704     void streamToIst(Stream inputStream, SerializationFormat format = defaultFormat)
1705     {
1706         _rootNode.deleteChildren;
1707         _mustRead = false;
1708         _stream = inputStream;
1709         _format = format;
1710 
1711         IstNode newNde = _rootNode;
1712         IstNode parent = _rootNode;
1713 
1714         FormatToken tok = readFormat(_format)(_stream, newNde);
1715         assert(tok == FormatToken.objBeg, to!string(tok));
1716 
1717         while(tok != FormatToken.end)
1718         {
1719             newNde = construct!IstNode;
1720             tok = readFormat(_format)(_stream, newNde);
1721             switch (tok)
1722             {
1723                 case FormatToken.prop:
1724                     parent.addChild(newNde);
1725                     newNde.info.level = cast(uint) newNde.level;
1726                     break;
1727                 case FormatToken.objBeg:
1728                     parent.addChild(newNde);
1729                     parent = newNde;
1730                     newNde.info.level = cast(uint) newNde.level;
1731                     break;
1732                 case FormatToken.objEnd:
1733                     assert(parent);
1734                     if (parent)
1735                         parent = parent.parent;
1736                     destruct(newNde);
1737                     break;
1738                 default:
1739                     destruct(newNde);
1740             }
1741         }
1742         //
1743         _stream = null;
1744     }
1745 
1746     /**
1747      * Finds the tree node matching to an identifier chain.
1748      *
1749      * Params:
1750      *      cache = Set to true to use a cache. In this case $(D updateCache())
1751      *      must be called before.
1752      *      descriptorName = The chain of properties name that identifies the node.
1753      * Returns:
1754      *      A reference to the node that matches to the property or nulll.
1755      */
1756     IstNode findNode(bool cache = false)(const(char)[] descriptorName)
1757     {
1758         if (_rootNode.info.name == descriptorName)
1759             return _rootNode;
1760 
1761         static if (cache)
1762             return _nodesCache.find(descriptorName);
1763         else
1764         {
1765             IstNode result;
1766             deepIterate!((IstNode n)
1767             {
1768                 if (n.identifiersChain() == descriptorName)
1769                 {
1770                     result = n;
1771                     return true;
1772                 }
1773                 else
1774                 {
1775                     result = null;
1776                     return false;
1777                 }
1778             }, "children")(_rootNode);
1779             return result;
1780         }
1781     }
1782 
1783     /**
1784      * Restores the IST from an arbitrary tree node.
1785      *
1786      * The process is lead by the nodeInfo associated to the node.
1787      * If the descriptor is not defined then the $(D wantDescriptorEvent) is called.
1788      * It means that this method can be used to deserialize to an arbitrary descriptor,
1789      * for example after a call to $(D streamToIst()).
1790      *
1791      * Params:
1792      *      node = The IST node from where the restoration starts.
1793      *      It can be determined by a call to $(D findNode()).
1794      *      recursive = When set to true the restoration is recursive.
1795      */
1796     void nodeToPublisher(IstNode node, bool recursive = false)
1797     {
1798         bool restore(IstNode current)
1799         {
1800             bool result = true;
1801             GenericDescriptor* des = cast(GenericDescriptor*) current.info.descriptor;
1802             if (des && PropHint.dontSet in des.hints)
1803                 return result;
1804             if (current.info.descriptor && current.info.name ==
1805                 (cast(PropDescriptor!byte*)current.info.descriptor).name)
1806                     nodeInfo2Declarator(current.info);
1807             else
1808             {
1809                 bool stop;
1810                 result = restoreFromEvent(current, stop);
1811                 result &= !stop;
1812             }
1813             return result;
1814         }
1815         bool restoreLoop(IstNode current)
1816         {
1817             if (!restore(current)) return false;
1818             foreach(child; current.children)
1819             {
1820                 auto childNode = cast(IstNode) child;
1821                 if (!restore(childNode)) return false;
1822                 if (recursive && (childNode.info.rtti.type == RtType._object ||
1823                     (childNode.info.rtti.type == RtType._struct &&
1824                     childNode.info.rtti.structInfo.type == StructType._publisher)))
1825                     if (!restoreLoop(childNode)) return false;
1826             }
1827             return true;
1828         }
1829         restoreLoop(node);
1830     }
1831 
1832     /**
1833      * Restores the property associated to an IST node using the setter of the
1834      * PropDescriptor passed as parameter.
1835      *
1836      * Params:
1837      *      node = An IstNode. Can be determined by a call to $(D findNode()).
1838      *      descriptor = The PropDescriptor whose setter is used to restore the node data.
1839      *      if not specified then the $(D onWantDescriptor) event is called.
1840      */
1841     void nodeToProperty(T)(IstNode node, PropDescriptor!T* descriptor = null)
1842     {
1843         if (descriptor && PropHint.dontSet in descriptor.hints)
1844             return;
1845         if (descriptorMatchesNode!T(descriptor, node))
1846         {
1847             node.info.descriptor = descriptor;
1848             nodeInfo2Declarator(node.info);
1849         }
1850         else
1851         {
1852             bool noop;
1853             restoreFromEvent(node, noop);
1854         }
1855     }
1856 
1857 //------------------------------------------------------------------------------
1858 //---- miscellaneous  ---------------------------------------------------------+
1859 
1860     /**
1861      * Updates the cache optionally used in $(D findNode()).
1862      */
1863     void updateCache()
1864     {
1865         _nodesCache.setRoot(this._rootNode);
1866     }
1867 
1868     /// The IST can be modified, build, cleaned from the root node
1869     @property IstNode serializationTree(){return _rootNode;}
1870 
1871     /// Event called when the serializer misses a property descriptor.
1872     @property WantDescriptorEvent onWantDescriptor(){return _onWantDescriptor;}
1873 
1874     /// ditto
1875     @property void onWantDescriptor(WantDescriptorEvent value){_onWantDescriptor = value;}
1876 
1877     /// Event called when the serializer misses an aggregate
1878     @property WantAggregateEvent onWantAggregate(){return _onWantAggregate;}
1879 
1880     /// ditto
1881     @property void onWantAggregate(WantAggregateEvent value){_onWantAggregate = value;}
1882 
1883     /// Event called when the serializer finishes to destream an aggregate.
1884     @property void onFinishAggregate(void delegate(void*) value){_onFinishAggregate = value;}
1885 
1886     /// ditto
1887     @property void delegate(void*) onFinishAggregate(){return _onFinishAggregate;}
1888 
1889 //------------------------------------------------------------------------------
1890 
1891 }
1892 ///
1893 unittest
1894 {
1895     // defines two serializable classes
1896     class B: PropertyPublisher
1897     {
1898         mixin PropertyPublisherImpl;
1899         @SetGet uint data1 = 1, data2 = 2;
1900         this(){collectPublications!B;}
1901         void reset(){data1 = 0; data2 = 0;}
1902     }
1903     class A: PropertyPublisher
1904     {
1905         mixin PropertyPublisherImpl;
1906         @SetGet B sub1, sub2;
1907         this()
1908         {
1909             sub1 = construct!B;
1910             sub2 = construct!B;
1911             // sub1 and sub2 are fully serialized because they already exist
1912             // when the analyzers run, otherwise they would be considered as
1913             // reference and their members would not be serialized.
1914             collectPublications!A;
1915         }
1916         ~this(){destructEach(sub1, sub2);}
1917     }
1918 
1919     MemoryStream stream = construct!MemoryStream;
1920     Serializer serializer = construct!Serializer;
1921     A a = construct!A;
1922     // serializes
1923     serializer.publisherToStream(a, stream);
1924 
1925     // reset the fields
1926     a.sub1.reset;
1927     a.sub2.reset;
1928     stream.position = 0;
1929     // deserializes
1930     serializer.streamToPublisher(stream, a);
1931     // check the restored values
1932     assert(a.sub1.data1 == 1);
1933     assert(a.sub2.data1 == 1);
1934     assert(a.sub1.data2 == 2);
1935     assert(a.sub2.data2 == 2);
1936     // cleanup
1937     destructEach(a, serializer, stream);
1938 }
1939 
1940 //----
1941 
1942 // Miscellaneous helpers ------------------------------------------------------+
1943 /**
1944  * Serializes a $(D PropertyPublisher) to a file.
1945  *
1946  * This helper function works in pair with $(D fileToPublisher()).
1947  *
1948  * Params:
1949  *      pub = The $(D PropertyPublisher) to save.
1950  *      filename = The target file, always created or overwritten.
1951  *      format = Optional, the serialization format, by default iztext.
1952  */
1953 void publisherToFile(Object pub, const(char)[] filename,
1954     SerializationFormat format = defaultFormat,
1955     WantAggregateEvent wae = null, WantDescriptorEvent wde = null)
1956 {
1957     MemoryStream str = construct!MemoryStream;
1958     Serializer ser = construct!Serializer;
1959     scope(exit) destructEach(str, ser);
1960     ser.onWantAggregate = wae;
1961     ser.onWantDescriptor = wde;
1962 
1963     ser.publisherToStream(pub, str, format);
1964     str.saveToFile(filename);
1965 }
1966 
1967 /**
1968  * Deserializes a file to a $(D PropertyPublisher).
1969  *
1970  * This helper function works in pair with $(D publisherToFile()).
1971  *
1972  * Params:
1973  *      filename = The source file.
1974  *      pub = The target $(D PropertyPublisher).
1975  *      format = optional, the serialization format, by default iztext.
1976  */
1977 void fileToPublisher(const(char)[] filename, Object pub,
1978     SerializationFormat format = defaultFormat,
1979     WantAggregateEvent wae = null, WantDescriptorEvent wde = null)
1980 {
1981     MemoryStream str = construct!MemoryStream;
1982     Serializer ser = construct!Serializer;
1983     scope(exit) destructEach(str, ser);
1984     ser.onWantAggregate = wae;
1985     ser.onWantDescriptor = wde;
1986 
1987     str.loadFromFile(filename);
1988     ser.streamToPublisher(str, pub, format);
1989 }
1990 //----
1991 
1992 version(unittest)
1993 {
1994     import std.stdio;
1995 
1996     unittest
1997     {
1998         char[] text;
1999         ubyte[] value;
2000         SerNodeInfo inf;
2001         //
2002         value = [13];
2003         text = "13".dup;
2004         inf.rtti = getRtti!(typeof(value[0]));
2005         inf.value = value ;
2006         assert(value2text(&inf) == text);
2007         assert(text2value(text, &inf) == value);
2008         //
2009         value = [13,14];
2010         text = "[13, 14]".dup;
2011         inf.rtti = getRtti!(typeof(value));
2012         inf.value = value ;
2013         assert(value2text(&inf) == text);
2014         assert(text2value(text, &inf) == value);
2015         //
2016         void testType(T)(T t)
2017         {
2018             char[] asText;
2019             T v = t;
2020             SerNodeInfo info;
2021             PropDescriptor!T descr;
2022             //
2023             descr.define(&v, "property");
2024             setNodeInfo!T(&info, &descr);
2025             //
2026             asText = to!string(v).dup;
2027             assert(value2text(&info) == asText, T.stringof);
2028             static if (!isArray!T)
2029                 assert(*cast(T*)(text2value(asText, &info)).ptr == v, T.stringof);
2030             static if (isArray!T)
2031                 assert(cast(ubyte[])text2value(asText, &info)==cast(ubyte[])v,T.stringof);
2032         }
2033 
2034         struct ImpConv{uint _field; alias _field this;}
2035         auto ic = ImpConv(8);
2036 
2037         testType('c');
2038         testType("azertyuiop".dup);
2039         testType!uint(ic);
2040         testType(cast(byte)8);      testType(cast(byte[])[8,8]);
2041         testType(cast(ubyte)8);     testType(cast(ubyte[])[8,8]);
2042         testType(cast(short)8);     testType(cast(short[])[8,8]);
2043         testType(cast(ushort)8);    testType(cast(ushort[])[8,8]);
2044         testType(cast(int)8);       testType(cast(int[])[8,8]);
2045         testType(cast(uint)8);      testType(cast(uint[])[8,8]);
2046         testType(cast(long)8);      testType(cast(long[])[8,8]);
2047         testType(cast(ulong)8);     testType(cast(ulong[])[8,8]);
2048         testType(cast(float).8f);   testType(cast(float[])[.8f,.8f]);
2049         testType(cast(double).8);   testType(cast(double[])[.8,.8]);
2050     }
2051 
2052     unittest
2053     {
2054         //foreach(fmt;EnumMembers!SerializationFormat)
2055         //    testByFormat!fmt();
2056 
2057         testByFormat!(SerializationFormat.iztxt)();
2058         testByFormat!(SerializationFormat.izbin)();
2059         //testByFormat!(SerializationFormat.json)();
2060     }
2061 
2062     class Referenced1 {}
2063 
2064     class ReferencedUser: PropertyPublisher
2065     {
2066         mixin PropertyPublisherImpl;
2067         mixin inheritedDtor;
2068 
2069         SerializableReference fSerRef;
2070         Referenced1 fRef;
2071 
2072         void doRestore(Object sender)
2073         {
2074             fRef = fSerRef.restoreReference!Referenced1;
2075         }
2076 
2077         this()
2078         {
2079             fSerRef = construct!SerializableReference;
2080             fSerRef.onRestored = &doRestore;
2081             collectPublications!ReferencedUser;
2082         }
2083 
2084         ~this() {destruct(fSerRef);}
2085 
2086         @Get SerializableReference theReference()
2087         {
2088             fSerRef.storeReference!Referenced1(fRef);
2089             return fSerRef;
2090         }
2091         @Set void theReference(SerializableReference value)
2092         {
2093             // when a sub publisher is owned the setter is a noop.
2094             // actually the serializer uses the descriptor getter
2095             // to know where the members of the sub pub. are located.
2096         }
2097     }
2098 
2099     class ClassA: ClassB
2100     {
2101         mixin inheritedDtor;
2102         private:
2103             ClassB _aB1, _aB2;
2104             PropDescriptor!Object* aB1descr, aB2descr;
2105         public:
2106             this()
2107             {
2108                 assert(!this.publicationFromName("aB1"));
2109                 assert(!this.publicationFromName("aB2"));
2110                 _aB1 = construct!ClassB;
2111                 _aB2 = construct!ClassB;
2112                 aB1descr = construct!(PropDescriptor!Object)(cast(Object*)&_aB1, "aB1");
2113                 aB2descr = construct!(PropDescriptor!Object)(cast(Object*)&_aB2, "aB2");
2114                 aB1descr.declarator = this;
2115                 aB2descr.declarator = this;
2116 
2117                 // add publications by hand.
2118                 _publishedDescriptors ~= cast(void*) aB1descr;
2119                 _publishedDescriptors ~= cast(void*) aB2descr;
2120 
2121                 // without the scanners ownership must be set manually
2122                 setTargetObjectOwner(aB1descr, this);
2123                 setTargetObjectOwner(aB2descr, this);
2124                 assert(targetObjectOwnedBy(aB1descr, this));
2125                 assert(targetObjectOwnedBy(aB2descr, this));
2126             }
2127             ~this()
2128             {
2129                 destructEach(_aB1, _aB2);
2130                 callInheritedDtor;
2131             }
2132             override void reset()
2133             {
2134                 super.reset;
2135                 _aB1.reset;
2136                 _aB2.reset;
2137             }
2138     }
2139 
2140     class ClassB : PropertyPublisher
2141     {
2142         mixin PropertyPublisherImpl;
2143         mixin inheritedDtor;
2144 
2145         private:
2146             Array!int  _anIntArray;
2147             Array!char _someChars;
2148             @SetGet float  aFloat;
2149 
2150         public:
2151 
2152             this()
2153             {
2154                 collectPublications!ClassB;
2155                 aFloat = 0.123456f;
2156                 _someChars = "azertyuiop";
2157                 anIntArray = [0, 1, 2, 3];
2158             }
2159 
2160             ~this()
2161             {
2162                 destructEach(_anIntArray, _someChars);
2163                 callInheritedDtor;
2164             }
2165 
2166             void reset()
2167             {
2168                 _anIntArray.length = 0;
2169                 _someChars.length = 0;
2170                 aFloat = 0.0f;
2171             }
2172 
2173             @Set anIntArray(int[] value){_anIntArray = value;}
2174             @Get int[] anIntArray(){return _anIntArray[];}
2175             @Set someChars(char[] value){_someChars = value;}
2176             @Get char[] someChars(){return _someChars[];}
2177     }
2178 
2179     void testByFormat(SerializationFormat format)()
2180     {
2181         ReferenceMan.clear;
2182 
2183         MemoryStream str  = construct!MemoryStream;
2184         Serializer ser    = construct!Serializer;
2185         ClassB b = construct!(ClassB);
2186         ClassA a = construct!(ClassA);
2187         scope(exit) destructEach(str, ser, b, a);
2188 
2189         // basic sequential store/restore ---+
2190         ser.publisherToStream(b,str,format);
2191         //str.saveToFile("a.txt");
2192         b.reset;
2193         assert(b.anIntArray == []);
2194         assert(b.aFloat == 0.0f);
2195         assert(b.someChars == "");
2196         str.position = 0;
2197         ser.streamToPublisher(str,b,format);
2198 
2199         str.clear;
2200         ser.serializationTree.saveToStream(str);
2201         //str.saveToFile("f.txt");
2202 
2203         assert(b.anIntArray == [0, 1, 2, 3], to!string(b.anIntArray));
2204         assert(b.aFloat == 0.123456f);
2205         assert(b.someChars == "azertyuiop");
2206         //----
2207 
2208         // arbitrarily find a prop ---+
2209         assert(ser.findNode!false("root.anIntArray"));
2210         assert(ser.findNode!false("root.aFloat"));
2211         assert(ser.findNode!false("root.someChars"));
2212         assert(!ser.findNode!false("root."));
2213         assert(!ser.findNode!false("aFloat"));
2214         assert(!ser.findNode!false("root.someChar"));
2215         assert(!ser.findNode!false(""));
2216 
2217         ser.updateCache;
2218         assert(ser.findNode!true("root.anIntArray"));
2219         assert(ser.findNode!true("root.aFloat"));
2220         assert(ser.findNode!true("root.someChars"));
2221         assert(!ser.findNode!true("root."));
2222         assert(!ser.findNode!true("aFloat"));
2223         assert(!ser.findNode!true("root.someChar"));
2224         assert(!ser.findNode!true(""));
2225         //----
2226 
2227         // restore elsewhere that in the declarator ---+
2228         float outside;
2229         auto node = ser.findNode("root.aFloat");
2230         auto aFloatDescr = PropDescriptor!float(&outside, "aFloat");
2231         ser.nodeToProperty(node, &aFloatDescr);
2232         assert(outside == 0.123456f);
2233         //----
2234 
2235         // nested declarations with super.declarations ---+
2236         str.clear;
2237         ser.publisherToStream(a,str,format);
2238         a.reset;
2239         assert(a.anIntArray == []);
2240         assert(a.aFloat == 0.0f);
2241         assert(a.someChars == "");
2242         assert(a._aB1.anIntArray == []);
2243         assert(a._aB1.aFloat == 0.0f);
2244         assert(a._aB1.someChars == "");
2245         assert(a._aB2.anIntArray == []);
2246         assert(a._aB2.aFloat == 0.0f);
2247         assert(a._aB2.someChars == "");
2248         str.position = 0;
2249 
2250         ser.streamToPublisher(str,a,format);
2251         assert(a.anIntArray == [0, 1, 2, 3]);
2252         assert(a.aFloat ==  0.123456f);
2253         assert(a.someChars == "azertyuiop");
2254         assert(a._aB1.anIntArray == [0, 1, 2, 3]);
2255         assert(a._aB1.aFloat ==  0.123456f);
2256         assert(a._aB1.someChars == "azertyuiop");
2257         assert(a._aB2.anIntArray == [0, 1, 2, 3]);
2258         assert(a._aB2.aFloat ==  0.123456f);
2259         assert(a._aB2.someChars == "azertyuiop");
2260         //----
2261 
2262         // store & restore a serializable reference ---+
2263         auto ref1 = construct!(Referenced1);
2264         auto ref2 = construct!(Referenced1);
2265         auto usrr = construct!(ReferencedUser);
2266         scope(exit) destructEach(ref1, ref2, usrr);
2267 
2268         assert(ReferenceMan.storeReference!Referenced1(ref1, "referenced.ref1"));
2269         assert(ReferenceMan.storeReference!Referenced1(ref2, "referenced.ref2"));
2270         assert(ReferenceMan.referenceID!Referenced1(ref1) == "referenced.ref1");
2271         assert(ReferenceMan.referenceID!Referenced1(ref2) == "referenced.ref2");
2272         assert(ReferenceMan.reference!Referenced1("referenced.ref1") == ref1);
2273         assert(ReferenceMan.reference!Referenced1("referenced.ref2") == ref2);
2274 
2275         str.clear;
2276         usrr.fRef = ref1;
2277         ser.publisherToStream(usrr, str, format);
2278 
2279         usrr.fRef = ref2;
2280         assert(usrr.fRef == ref2);
2281         str.position = 0;
2282         ser.streamToPublisher(str, usrr, format);
2283         assert(usrr.fRef == ref1);
2284 
2285         usrr.fRef = null;
2286         assert(usrr.fRef is null);
2287         str.position = 0;
2288         ser.streamToPublisher(str, usrr, format);
2289         assert(usrr.fRef is ref1);
2290 
2291         str.clear;
2292         usrr.fRef = null;
2293         ser.publisherToStream(usrr, str, format);
2294         usrr.fRef = ref2;
2295         assert(usrr.fRef is ref2);
2296         str.position = 0;
2297         ser.streamToPublisher(str, usrr, format);
2298         assert(usrr.fRef is null);
2299         //----
2300 
2301         // auto store, stream to ist, restores manually ---+
2302         str.clear;
2303         ser.publisherToStream(b,str,format);
2304         b.reset;
2305         assert(b.anIntArray == []);
2306         assert(b.aFloat == 0.0f);
2307         assert(b.someChars == "");
2308         str.position = 0;
2309         ser.streamToIst(str,format);
2310         //
2311         auto node_anIntArray = ser.findNode("root.anIntArray");
2312         if(node_anIntArray) ser.nodeToProperty(node_anIntArray,
2313              b.publication!(int[])("anIntArray"));
2314         else assert(0);
2315         auto node_aFloat = ser.findNode("root.aFloat");
2316         if(node_aFloat) ser.nodeToProperty(node_aFloat,
2317             b.publication!float("aFloat"));
2318         else assert(0);
2319         auto node_someChars = ser.findNode("root.someChars");
2320         if(node_someChars) ser.nodeToProperty(node_someChars,
2321            b.publication!(char[])("someChars"));
2322         else assert(0);
2323         assert(b.anIntArray == [0, 1, 2, 3]);
2324         assert(b.aFloat == 0.123456f);
2325         assert(b.someChars == "azertyuiop");
2326         //----
2327 
2328         // decomposed de/serialization phases with event ---+
2329         void wantDescr(IstNode node, ref Ptr matchingDescriptor, out bool stop)
2330         {
2331             immutable string chain = node.parentIdentifiersChain;
2332             if (chain == "root")
2333                 matchingDescriptor = a.publicationFromName(node.info.name[]);
2334             else if (chain == "root.aB1")
2335                 matchingDescriptor = a._aB1.publicationFromName(node.info.name[]);
2336             else if (chain == "root.aB2")
2337                 matchingDescriptor = a._aB2.publicationFromName(node.info.name[]);
2338         }
2339 
2340         str.clear;
2341         ser.publisherToIst(a);
2342         ser.istToStream(str,format);
2343         a.reset;
2344         assert(a.anIntArray == []);
2345         assert(a.aFloat == 0.0f);
2346         assert(a.someChars == "");
2347         assert(a._aB1.anIntArray == []);
2348         assert(a._aB1.aFloat == 0.0f);
2349         assert(a._aB1.someChars == "");
2350         assert(a._aB2.anIntArray == []);
2351         assert(a._aB2.aFloat == 0.0f);
2352         assert(a._aB2.someChars == "");
2353         str.position = 0;
2354         ser.onWantDescriptor = &wantDescr;
2355         ser.streamToIst(str,format);
2356         auto nd = ser.findNode("root");
2357         assert(nd);
2358         ser.nodeToPublisher(nd, true);
2359         assert(a.anIntArray == [0, 1, 2, 3]);
2360         assert(a.aFloat ==  0.123456f);
2361         assert(a.someChars == "azertyuiop");
2362         assert(a._aB1.anIntArray == [0, 1, 2, 3]);
2363         assert(a._aB1.aFloat ==  0.123456f);
2364         assert(a._aB1.someChars == "azertyuiop");
2365         assert(a._aB2.anIntArray == [0, 1, 2, 3]);
2366         assert(a._aB2.aFloat ==  0.123456f);
2367         assert(a._aB2.someChars == "azertyuiop");
2368         ser.onWantDescriptor = null;
2369         // ----
2370 
2371         // struct serialized as basicType ---+
2372         import iz.enumset: EnumSet, Set8;
2373         enum A {a0,a1,a2}
2374         alias SetofA = EnumSet!(A,Set8);
2375 
2376         class Bar: PropertyPublisher
2377         {
2378             mixin PropertyPublisherImpl;
2379 
2380             private:
2381                 SetofA _set;
2382                 PropDescriptor!SetofA setDescr;
2383             public:
2384                 this()
2385                 {
2386                     setDescr.define(&_set,"set");
2387                     with(A) _set = SetofA(a1,a2);
2388                     collectPublications!Bar;
2389                 }
2390                 // struct can only be serialized using a representation
2391                 // whose type is iteself serializable
2392                 @Set void set(ubyte value){_set = value;}
2393                 @Get ubyte set(){return _set.container;}
2394         }
2395 
2396         str.clear;
2397         auto bar = construct!Bar;
2398         scope(exit) bar.destruct;
2399 
2400         ser.publisherToStream(bar, str, format);
2401         bar._set = [];
2402         str.position = 0;
2403         ser.streamToPublisher(str, bar, format);
2404         assert( bar._set == SetofA(A.a1,A.a2), to!string(bar.set));
2405         // ----
2406     }
2407 
2408     // test fields renamed between two versions ---+
2409     class Source: PropertyPublisher
2410     {
2411         @GetSet private uint _a = 78;
2412         @GetSet private char[] _b = "foobar".dup;
2413         mixin PropertyPublisherImpl;
2414         this(){collectPublications!Source;}
2415     }
2416 
2417     class Target: PropertyPublisher
2418     {
2419         @GetSet private int _c;
2420         @GetSet private ubyte[] _d;
2421         mixin PropertyPublisherImpl;
2422         this(){collectPublications!Target;}
2423     }
2424 
2425     unittest
2426     {
2427         Source source = construct!Source;
2428         Target target = construct!Target;
2429         Serializer ser = construct!Serializer;
2430         MemoryStream str = construct!MemoryStream;
2431         scope(exit) destructEach(source, ser, str, target);
2432 
2433         ser.publisherToStream(source, str, SerializationFormat.izbin);
2434         str.position = 0;
2435 
2436         void error(IstNode node, ref Ptr matchingDescriptor, out bool stop)
2437         {
2438             if (node.info.name == "a")
2439                 matchingDescriptor = target.publication!(int)("c");
2440             else if (node.info.name == "b")
2441                 matchingDescriptor = target.publication!(ubyte[])("d");
2442             stop = false;
2443         }
2444         ser.onWantDescriptor = &error;
2445         ser.streamToPublisher(str, target, SerializationFormat.izbin);
2446         assert(target._c == 78);
2447         assert(cast(char[])target._d == "foobar");
2448     }
2449     //----
2450 
2451     // test the RuntimeTypeInfo-based serialization ----+
2452     enum E:byte {e0 = 1, e1 = 2}
2453 
2454     class SubPublisher: PropertyPublisher
2455     {
2456         // fully serialized (initializer is MainPub)
2457         mixin PropertyPublisherImpl;
2458         @SetGet char[] _someChars = "awhyes".dup;
2459         this(){collectPublicationsFromFields!SubPublisher;}
2460     }
2461 
2462     class RefPublisher: PropertyPublisher
2463     {
2464         // only ref is serialized (initializer is not MainPub)
2465         mixin PropertyPublisherImpl;
2466         this(){collectPublicationsFromFields!RefPublisher;}
2467         @SetGet uint _a;
2468     }
2469 
2470     class MainPublisher: PropertyPublisher
2471     {
2472         mixin PropertyPublisherImpl;
2473         mixin inheritedDtor;
2474 
2475         // target when _subPublisher wont be found
2476         SubPublisher _anotherSubPubliser;
2477 
2478         // the sources for the references
2479         void delegate(uint) _delegateSource;
2480         RefPublisher _refPublisherSource;
2481         string dgTest;
2482 
2483         @SetGet E _e;
2484         @SetGet ubyte _a = 12;
2485         @SetGet byte _b = 21;
2486         @SetGet byte _c = 31;
2487         @SetGet void delegate(uint) _delegate;
2488         MemoryStream _stream;
2489 
2490         Array!char _t;
2491         @Set t(char[] value){_t = value;}
2492         @Get char[] t(){return _t;}
2493 
2494         @SetGet RefPublisher _refPublisher; //initially null, so it's a ref.
2495         @SetGet SubPublisher _subPublisher; //initially assigned so 'this' is the owner.
2496 
2497         this()
2498         {
2499             _t = "line1\"inside dq\"\nline2\nline3".dup;
2500             _refPublisherSource = construct!RefPublisher; // not published
2501             _subPublisher = construct!SubPublisher;
2502             _anotherSubPubliser = construct!SubPublisher;
2503             _stream = construct!MemoryStream;
2504             _stream.writeUbyte(0XFE);
2505             _stream.writeUbyte(0XFD);
2506             _stream.writeUbyte(0XFC);
2507             _stream.writeUbyte(0XFB);
2508             _stream.writeUbyte(0XFA);
2509             _stream.writeUbyte(0XF0);
2510 
2511             // collect publications before ref are assigned
2512             collectPublications!MainPublisher;
2513 
2514             _delegateSource = &delegatetarget;
2515             _delegate = _delegateSource;
2516             _refPublisher = _refPublisherSource; // e.g assingation during runtime
2517 
2518             assert(_refPublisher.declarator !is this);
2519             assert(_refPublisher.declarator is null);
2520 
2521             auto dDescr = publication!GenericDelegate("delegate", false);
2522             assert(dDescr);
2523 
2524             auto strDesc = publicationFromName("stream");
2525             assert(strDesc);
2526 
2527             ReferenceMan.storeReference(_refPublisherSource,
2528                 "root.refPublisher");
2529             ReferenceMan.storeReference(&_delegateSource,
2530                 "root.delegate");
2531 
2532             alias DGT = typeof(_delegateSource);
2533             assert(*cast(DGT*) ReferenceMan.reference(DGT.stringof, "root.delegate") ==
2534                 _delegateSource);
2535 
2536             assert(publicationFromName("delegate") != null);
2537         }
2538         ~this()
2539         {
2540             destruct(_refPublisherSource);
2541             destruct(_anotherSubPubliser);
2542             destruct(_stream);
2543             destruct(_t);
2544             callInheritedDtor;
2545         }
2546         void delegatetarget(uint param){dgTest = "awyesss";}
2547         void reset()
2548         {
2549             _e = E.e1;
2550             _a = 0; _b = 0; _c = 0; _t = _t.init;
2551             _subPublisher.destruct;
2552             _subPublisher = null; // wont be found anymore during deser.
2553             _anotherSubPubliser._someChars = "".dup;
2554             _delegate = null;
2555             _refPublisher = null;
2556             _stream.size = 0;
2557         }
2558         @Get Stream stream()
2559         {
2560             return _stream;
2561         }
2562         @Set void stream(Stream str)
2563         {
2564             str.position = 0;
2565             _stream.loadFromStream(str);
2566             _stream.position = 0;
2567             assert(str.size > 0);
2568         }
2569     }
2570 
2571     unittest
2572     {
2573         MainPublisher c = construct!MainPublisher;
2574         Serializer ser = construct!Serializer;
2575         MemoryStream str = construct!MemoryStream;
2576         scope(exit) destructEach(c, ser, str);
2577 
2578         void objectNotFound(IstNode node, ref void* serializable, out bool fromReference)
2579         {
2580             if (node.info.name == "subPublisher")
2581             {
2582                 serializable = cast(void*) c._anotherSubPubliser;
2583             }
2584             if (node.info.name == "refPublisher")
2585                 fromReference = true;
2586         }
2587 
2588         ser.onWantAggregate = &objectNotFound;
2589         ser.publisherToStream(c, str);
2590         //str.saveToFile(r"test.txt");
2591         //
2592         c.reset;
2593         str.position = 0;
2594         ser.streamToPublisher(str, c);
2595         //
2596         assert(c._a == 12);
2597         assert(c._b == 21);
2598         assert(c._c == 31);
2599         assert(c._e == E.e0);
2600         assert(c._t == "line1\"inside dq\"\nline2\nline3", c._t);
2601         assert(c._refPublisher == c._refPublisherSource);
2602         assert(c._anotherSubPubliser._someChars == "awhyes");
2603         assert(c._delegate);
2604         assert(c._delegate.funcptr == c._delegateSource.funcptr);
2605         assert(c._delegate.ptr == c._delegateSource.ptr);
2606 
2607         c._delegate(123);
2608         assert(c.dgTest == "awyesss");
2609 
2610         assert(c._stream.readUbyte == 0xFE);
2611         assert(c._stream.readUbyte == 0xFD);
2612         assert(c._stream.readUbyte == 0xFC);
2613         assert(c._stream.readUbyte == 0xFB);
2614         assert(c._stream.readUbyte == 0xFA);
2615         assert(c._stream.readUbyte == 0xF0);
2616     }
2617     //----
2618 
2619     // test generic Reference restoring ---+
2620     class HasGenRef: PropertyPublisher
2621     {
2622         // the source, usually comes from outside
2623         Object source;
2624         // what's gonna be assigned
2625         Object target;
2626         mixin PropertyPublisherImpl;
2627         this()
2628         {
2629             collectPublications!HasGenRef;
2630             source = construct!Object;
2631             ReferenceMan.storeReference!void(cast(void*)source,"thiswillwork");
2632             target = source;
2633         }
2634         ~this()
2635         {
2636             destruct(source);
2637         }
2638 
2639         @Get const(char[]) objectReference()
2640         {
2641             // get ID from what's currently assigned
2642             return ReferenceMan.referenceID!void(cast(void*)target);
2643         }
2644 
2645         @Set void objectReference(const(char[]) value)
2646         {
2647             // ID -> Reference -> assign the variable
2648             target = cast(Object) ReferenceMan.reference!void(value);
2649         }
2650     }
2651 
2652     unittest
2653     {
2654         MemoryStream str = construct!MemoryStream;
2655         Serializer ser = construct!Serializer;
2656         HasGenRef obj = construct!HasGenRef;
2657         scope(exit) destructEach(ser, str, obj);
2658 
2659         ser.publisherToStream(obj, str);
2660         str.position = 0;
2661         obj.target = null;
2662 
2663         ser.streamToPublisher(str, obj);
2664         assert(obj.target == obj.source);
2665     }
2666     //----
2667 
2668     // test publishing struct
2669     unittest
2670     {
2671         static struct PubStruct
2672         {
2673             mixin PropertyPublisherImpl;
2674 
2675             @SetGet uint _ui = 8;
2676             @SetGet char[] _cs = "8".dup;
2677         }
2678 
2679         PubStruct ps;
2680         ps.collectPublications!PubStruct;
2681 
2682         assert(ps.publicationFromName("ui") != null);
2683         assert(ps.publicationFromName("cs") != null);
2684 
2685         MemoryStream str = construct!MemoryStream;
2686         Serializer ser = construct!Serializer;
2687         scope(exit) destructEach(str, ser);
2688         //
2689         ser.publisherToStream(ps, str);
2690         ps._cs = ps._cs.init;
2691         ps._ui = 0;
2692         //
2693         str.position = 0;
2694         ser.streamToPublisher(str, ps);
2695         assert(ps._cs == "8");
2696         assert(ps._ui == 8);
2697     }
2698 
2699     // test text struct
2700     unittest
2701     {
2702         static struct TextStruct
2703         {
2704             const(char)[] _value = "content backup".dup;
2705             const(char)[] saveToText(){return _value;}
2706             void loadFromText(const(char)[] value){_value = value.dup;}
2707         }
2708 
2709         class TextStructParent: PropertyPublisher
2710         {
2711             mixin PropertyPublisherImpl;
2712             @SetGet TextStruct _ts;
2713         }
2714 
2715         TextStructParent tsp = construct!TextStructParent;
2716         tsp.collectPublications!TextStructParent;
2717         assert(tsp.publicationFromName("ts") != null);
2718         assert(tsp.publicationFromIndex(0) != null);
2719         assert(tsp._ts._value == "content backup");
2720 
2721         MemoryStream str = construct!MemoryStream;
2722         Serializer ser = construct!Serializer;
2723         scope(exit) destructEach(str, ser, tsp);
2724 
2725         ser.publisherToStream(tsp, str);
2726         //str.saveToFile("textstruct.txt");
2727         tsp._ts._value = "";
2728 
2729         str.position = 0;
2730         ser.streamToPublisher(str, tsp);
2731         assert(tsp._ts._value == "content backup", tsp._ts._value);
2732     }
2733 
2734     // test bin struct
2735     unittest
2736     {
2737         static struct BinStruct
2738         {
2739             ubyte[] _value = cast(ubyte[])"content backup".dup;
2740             ubyte[] saveToBytes(){return _value;}
2741             void loadFromBytes(ubyte[] value){_value = value;}
2742         }
2743 
2744         class BinStructParent: PropertyPublisher
2745         {
2746             mixin PropertyPublisherImpl;
2747             @SetGet BinStruct _bs;
2748         }
2749 
2750         BinStructParent bsp = construct!BinStructParent;
2751         bsp.collectPublications!BinStructParent;
2752         assert(bsp.publicationFromName("bs") != null);
2753         assert(bsp.publicationFromIndex(0) != null);
2754         assert(bsp._bs._value == "content backup");
2755 
2756         MemoryStream str = construct!MemoryStream;
2757         Serializer ser = construct!Serializer;
2758         scope(exit) destructEach(str, ser, bsp);
2759 
2760         ser.publisherToStream(bsp, str);
2761         //str.saveToFile("binstruct.txt");
2762         bsp._bs._value = bsp._bs._value.init;
2763 
2764         str.position = 0;
2765         ser.streamToPublisher(str, bsp);
2766         assert(bsp._bs._value == "content backup");
2767     }
2768 
2769     // test nested publishing structs, detected from fields
2770     unittest
2771     {
2772         static struct Child
2773         {
2774             mixin PropertyPublisherImpl;
2775             @SetGet int _a = 8;
2776             @SetGet int _b = 9;
2777         }
2778         static struct Parent
2779         {
2780             mixin PropertyPublisherImpl;
2781             @SetGet Child _child1;
2782             @SetGet Child _child2;
2783         }
2784 
2785         Serializer ser = construct!Serializer;
2786         MemoryStream str = construct!MemoryStream;
2787         scope(exit) destructEach(str, ser);
2788         Parent parent;
2789         parent.collectPublications!Parent;
2790         parent._child1.collectPublications!Child;
2791         parent._child2.collectPublications!Child;
2792 
2793         assert(parent.publicationFromName("child1") != null);
2794         assert(parent.publicationFromName("child2") != null);
2795         assert(parent._child1.publicationFromName("a") != null);
2796         assert(parent._child2.publicationFromName("b") != null);
2797 
2798         ser.publisherToStream(parent, str);
2799         parent._child1._a = 0;
2800         parent._child1._b = 0;
2801         parent._child2._a = 0;
2802         parent._child2._b = 0;
2803 
2804         str.position = 0;
2805         ser.streamToPublisher(str, parent);
2806         assert(parent._child1._a == 8);
2807         assert(parent._child1._b == 9);
2808         assert(parent._child2._a == 8);
2809         assert(parent._child2._b == 9);
2810     }
2811 
2812     // test nested publishing structs, detected from get/set pair
2813     unittest
2814     {
2815         static struct Child
2816         {
2817             mixin PropertyPublisherImpl;
2818             @SetGet int _a = 8;
2819             @SetGet int _b = 9;
2820         }
2821         static struct Parent
2822         {
2823             mixin PropertyPublisherImpl;
2824             Child _child1;
2825             @Get ref Child child1(){return _child1;}
2826             @Set void child1(Child value) {}
2827             ~this() {destruct(_child1);}
2828         }
2829 
2830         Serializer ser = construct!Serializer;
2831         MemoryStream str = construct!MemoryStream;
2832         scope(exit) destructEach(str, ser);
2833         Parent parent;
2834         parent.collectPublications!Parent;
2835         parent._child1.collectPublications!Child;
2836 
2837         assert(parent.publicationFromName("child1") != null);
2838         assert(parent._child1.publicationFromName("a") != null);
2839         assert(parent._child1.publicationFromName("b") != null);
2840 
2841         ser.publisherToStream(parent, str);
2842         parent._child1._a = 0;
2843         parent._child1._b = 0;
2844 
2845         str.position = 0;
2846         ser.streamToPublisher(str, parent);
2847         assert(parent._child1._a == 8);
2848         assert(parent._child1._b == 9);
2849     }
2850 
2851     // PropHints
2852     unittest
2853     {
2854         class Foo: PropertyPublisher
2855         {
2856             mixin PropertyPublisherImpl;
2857 
2858             @PropHints(PropHint.dontGet)
2859             @SetGet int _i = 1;
2860             @PropHints(PropHint.dontSet)
2861             @SetGet int _j = 1;
2862             @PropHints(PropHint.initCare)
2863             @SetGet int _k = 0;
2864 
2865             this()
2866             {
2867                 collectPublications!Foo;
2868             }
2869         }
2870 
2871         Foo foo = construct!Foo;
2872         MemoryStream str = construct!MemoryStream;
2873         Serializer ser = construct!Serializer;
2874         scope(exit) destructEach(foo, ser, str);
2875 
2876         ser.publisherToStream(foo, str);
2877         assert(ser.findNode("root.i") is null);  // dontGet, so not in IST
2878 
2879         assert(ser.findNode("root.k") !is null); // _k was equal to 0
2880         assert(ser.findNode("root.j") !is null); // in IST...
2881 
2882         foo._i = 0;
2883         foo._j = 0;
2884         foo._k = 1;
2885         str.position = 0;
2886         ser.streamToPublisher(str, foo);
2887         assert(foo._k == 0);
2888         assert(foo._i == 0);
2889         assert(foo._j == 0); //...but not restored
2890     }
2891 }
2892