1 /**
2  * High-level iz classes.
3  */
4 module iz.classes;
5 
6 import
7     core.thread;
8 version(Posix)
9     import core.sys.posix.poll;
10 import
11     std.traits, std..string, std.algorithm, std.array, std.range, std.process,
12     std.datetime, std.stdio;
13 import
14     iz.types, iz.memory, iz.containers, iz.streams, iz.properties,
15     iz.serializer, iz.referencable, iz.observer, iz.strings, iz.rtti;
16 
17 
18 private template isAAKeyValid(T, string field)
19 if (is(T == class))
20 {
21     bool check()
22     {
23         bool result;
24         if (field.length) foreach(member; __traits(allMembers, T))
25         {
26             if (member == field)
27             {
28                 result = true;
29                 break;
30             }
31         }
32         return result;
33     }
34     static if (field.length)
35         enum isAAKeyValid = check();
36     else
37         enum isAAKeyValid = false;
38 }
39 
40 /**
41  * The PublishedObjectArray class template allows to serialize an array of
42  * PropertyPublisher.
43  *
44  * A Serializer is not able to directly handle object arrays but this class
45  * does the task automatically by managing the internal list of publications.
46  *
47  * The life-time of the objects is automatically handled by the internal container.
48  *
49  * Params:
50  *      ItemClass = The common items type. It must be a PropertyPublisher descendant.
51  *      managed = When false, the items lifetime is not managed.
52  *      aaKey = The member of ItemClass used to build an associative array that
53  *          indexes each item with the value of this member.
54  */
55 class PublishedObjectArray(ItemClass, bool managed = true, string aaKey = ""): PropertyPublisher
56 if (is(ItemClass : PropertyPublisher))
57 {
58 
59     mixin inheritedDtor;
60     mixin PropertyPublisherImpl;
61 
62 private:
63 
64     static if (isAAKeyValid!(ItemClass, aaKey))
65     {
66         alias KeyType = typeof(__traits(getMember, ItemClass, aaKey));
67         HashMap_LP!(KeyType, ItemClass) _aa;
68     }
69 
70 protected:
71 
72     @NoGc static immutable string _fmtName = "item<%d>";
73     @NoGc Array!ItemClass _items;
74 
75 public:
76 
77     ///
78     this()
79     {
80         collectPublications!(PublishedObjectArray!(ItemClass, managed, aaKey));
81     }
82 
83     ~this()
84     {
85         clear;
86         destruct(_items);
87         static if (isAAKeyValid!(ItemClass, aaKey))
88             destruct(_aa);
89         callInheritedDtor();
90     }
91 
92     /**
93      * Instantiates and returns a new item.
94      * Params:
95      *      a = When managed, the variadic arguments passed to the item $(D __ctor)
96      *      otherwise the item to add.
97      */
98     ItemClass addItem(A...)(A a)
99     in
100     {
101         assert(_items.length <= uint.max);
102     }
103     body
104     {
105         static if (managed)
106             _items ~= construct!ItemClass(a);
107         else
108             _items ~= a;
109 
110         PropDescriptor!Object* descr = construct!(PropDescriptor!Object);
111         descr.define(cast(Object*)&_items[$-1], format(_fmtName,_items.length-1), this);
112         _items[$-1].declarator = this;
113         _publishedDescriptors ~= descr;
114 
115         return _items[$-1];
116     }
117 
118     /**
119      * Removes and destroys an item from the internal container.
120      * Params:
121      *      t = Either the item to delete or its index.
122      */
123     void deleteItem(T)(T t)
124     if (isIntegral!T || is(Unqual!T == ItemClass))
125     {
126         ptrdiff_t index;
127         static if(is(Unqual!T == ItemClass))
128             index = _items.range.countUntil(t);
129         else index = cast(ptrdiff_t)t;
130 
131         if (_items.length == 0 || index > _items.length-1 || index < 0)
132             return;
133 
134         auto itm = _items[index];
135         static if (managed)
136             destruct(itm);
137         _items = _items[0..index] ~ _items[index+1..$];
138 
139         if (auto descr = publication!uint(format(_fmtName,index)))
140         {
141             // find index: a descendant may add descriptors in its this()
142             auto descrIndex = _publishedDescriptors[].countUntil(descr);
143             assert(descrIndex != -1);
144             _publishedDescriptors = _publishedDescriptors[0..descrIndex] ~ _publishedDescriptors[descrIndex+1 .. $];
145             destruct(descr);
146         }
147     }
148 
149     /**
150      * Sets or gets the item count.
151      *
152      * Items are automatically created or destroyed when changing this property.
153      * Note that to change the length of $(D items()) is a noop, the only way to
154      * add and remove items is to use $(D count()), $(D addItem())
155      * or $(D deleteItem()). When the content is not managed, the setter is not
156      * able to add new items and an error is raised.
157      */
158     @Get final uint count()
159     {
160         return cast(uint) _items.length;
161     }
162 
163     /// ditto
164     @Set final void count(uint aValue)
165     {
166         if (_items.length > aValue)
167             while (_items.length != aValue) deleteItem(_items.length-1);
168         else if (_items.length < aValue)
169         {
170             static if (managed)
171                 while (_items.length != aValue) addItem;
172             else
173                 assert(0);
174         }
175     }
176 
177     /**
178      * Provides an access to the items.
179      */
180     final ItemClass[] items()
181     {
182         return _items[];
183     }
184 
185     /**
186      * Clears the internal container and destroys the items.
187      */
188     void clear()
189     {
190         foreach_reverse(immutable i; 0 .. _items.length)
191             deleteItem(i);
192     }
193 
194     /// Support for accessing an indexed item with $(D []).
195     ItemClass opIndex(size_t i)
196     in
197     {
198         assert(i < _items.length);
199     }
200     body
201     {
202         return _items[i];
203     }
204 
205     /// Support for iterating the items with $(D foreach()).
206     int opApply(int delegate(ItemClass) dg)
207     {
208         int result;
209         foreach(immutable i; 0 .. _items.length)
210         {
211             result = dg(_items[i]);
212             if (result) break;
213         }
214         return result;
215     }
216 
217     /// Support for iterating the items with $(D foreach_reverse()).
218     int opApplyReverse(int delegate(ItemClass) dg)
219     {
220         int result;
221         foreach_reverse(immutable i; 0 .. _items.length)
222         {
223             result = dg(_items[i]);
224             if (result) break;
225         }
226         return result;
227     }
228 
229     /// Same as $(D items()).
230     ItemClass[] opSlice()
231     {
232         return _items[];
233     }
234 
235     /// Same as $(D items()) but with bounds.
236     ItemClass[] opSlice(size_t lo, size_t hi)
237     in
238     {
239         assert(lo <= hi);
240         assert(hi < _items.length);
241     }
242     body
243     {
244         return _items[][lo .. hi];
245     }
246 
247     /**
248      * Returns a pointer to the item whose the member that matches to the
249      * parameter passed as key during instanciation as the same value as $(D key).
250      */
251     static if (isAAKeyValid!(ItemClass, aaKey))
252     const(ItemClass)* opBinaryRight(string op : "in", K)(auto ref K key)
253     {
254         return key in _aa;
255     }
256 
257     /**
258      * Updates the associative array that exists when the member passed as key
259      * during the instanciation is not empty.
260      */
261     static if (isAAKeyValid!(ItemClass, aaKey))
262     void updateAA()
263     {
264         _aa.clear;
265         _aa.reserve(_items.length);
266         enum getValue = "_aa.insert!false(item." ~ aaKey ~ ", item);";
267         foreach(item; _items)
268             mixin(getValue);
269     }
270 
271 }
272 ///
273 unittest
274 {
275     class Item : PropertyPublisher
276     {
277         mixin PropertyPublisherImpl;
278         mixin inheritedDtor;
279         @SetGet uint _a, _b, _c;
280         // a default ctor is needed.
281         this(){collectPublications!Item;}
282         this(uint a, uint b, uint c)
283         {
284             _a = a; _b = b; _c = c;
285             collectPublications!Item;
286         }
287     }
288 
289     auto itemArray = construct!(PublishedObjectArray!Item);
290     itemArray.addItem(1, 2, 3);
291     itemArray.addItem(4, 5, 6);
292     // serializes the object array to a file
293     version(none) publisherToFile(itemArray, "backup.txt");
294     destruct(itemArray);
295 }
296 
297 unittest
298 {
299     class ItemT: PropertyPublisher
300     {
301         mixin PropertyPublisherImpl;
302         mixin inheritedDtor;
303 
304         @SetGet char[] name;
305         @SetGet int value;
306 
307         this()
308         {
309             collectPublications!ItemT;
310         }
311 
312         this(string name, int value)
313         {
314             collectPublications!ItemT;
315             this.name = name.dup;
316             this.value = value;
317         }
318     }
319 
320     alias ArrType = PublishedObjectArray!(ItemT, true, "name");
321     ArrType arr = construct!(ArrType);
322     arr.addItem("item0", 0);
323     arr.addItem("item1", 1);
324     arr.updateAA;
325     assert("item0" in arr);
326     assert("item1" in arr);
327     assert("item2" !in arr);
328     destruct(arr);
329 }
330 
331 unittest
332 {
333     class Item : PropertyPublisher
334     {
335         mixin PropertyPublisherImpl;
336         @SetGet uint _a, _b, _c;
337         this(){collectPublications!Item;}
338         void setProps(uint a, uint b, uint c)
339         {_a = a; _b = b; _c = c;}
340     }
341 
342     alias ItemCollection = PublishedObjectArray!Item;
343 
344     ItemCollection col = construct!ItemCollection;
345     MemoryStream str = construct!MemoryStream;
346     Serializer ser = construct!Serializer;
347     scope(exit) destructEach(col, ser, str);
348 
349     Item itm = col.addItem();
350     itm.setProps(0u,1u,2u);
351     itm = col.addItem;
352     itm.setProps(3u,4u,5u);
353     itm = col.addItem;
354     itm.setProps(6u,7u,8u);
355     //
356     auto collaccess = col.items;
357     collaccess.length = 0;
358     assert(col.items.length == 3);
359     //
360     ser.publisherToStream(col, str, SerializationFormat.iztxt);
361     str.position = 0;
362     col.clear;
363     assert(col.items.count == 0);
364 
365     ser.streamToPublisher(str, col, SerializationFormat.iztxt);
366     assert(col._publishedDescriptors.length == 4); // 3 + count descr
367     col.deleteItem(0);
368     assert(col._publishedDescriptors.length == 3); // 2 + count descr
369     assert(col.items.count == 2);
370     assert(col[0]._c == 5u);
371     assert(col[1]._c == 8u);
372     col.items[1]._c = 7u;
373 
374     auto todelete = col.items[0];
375     col.deleteItem(todelete);
376     assert(col.items.count == 1);
377     col.count = 0;
378     col.clear;
379     assert(col.items.count == 0);
380 }
381 
382 
383 /**
384  * The PublishedAA class template allows to serialize an associative array.
385  *
386  * A Serializer is not able to directly handle $(I AA)s but this class
387  * does the task automatically by splitting keys and values in two arrays.
388  *
389  * This class template can only be instantiated if the $(I AA) key and value
390  * type is basic (see iz.rtti.BasicRtTypes).
391  *
392  * Params:
393  *      AA = The type of the associative array.
394  */
395 final class PublishedAA(AA): PropertyPublisher
396 if (isAssociativeArray!AA && isSerializable!(KeyType!AA) &&
397     isSerializable!(ValueType!AA))
398 {
399 
400     mixin inheritedDtor;
401     mixin PropertyPublisherImpl;
402 
403 protected:
404 
405     @NoGc AA* _source;
406     @NoGc Array!(KeyType!AA) _keys;
407     @NoGc Array!(ValueType!AA) _content;
408 
409     uint _setCount, _getCount;
410 
411     void doSet()
412     {
413         if (_setCount++ % 2 == 0 && _setCount > 0)
414             toAA(*_source);
415     }
416 
417     void doGet()
418     {
419         if (_getCount++ %  2 == 0)
420             fromAA(*_source);
421     }
422 
423     @Set void keys(KeyType!AA[] value)
424     {
425         _keys = value;
426         if (_source) doSet;
427     }
428 
429     @Get KeyType!AA[] keys()
430     {
431         if (_source) doGet;
432         return _keys[];
433     }
434 
435     @Set void values(ValueType!AA[] value)
436     {
437         _content = value;
438         if (_source) doSet;
439     }
440 
441     @Get ValueType!AA[] values()
442     {
443         if (_source) doGet;
444         return _content[];
445     }
446 
447 public:
448 
449     ///
450     this()
451     {
452         collectPublications!(PublishedAA!AA);
453     }
454 
455     /**
456      * Constructs a new instance and sets a reference to the source $(I AA).
457      * Using this constructor, $(D toAA()) and $(D fromAA()) don't need
458      * to be called manually.
459      *
460      * Params:
461      *      aa = A pointer to the associative array that will be stored and restored.
462      */
463     this(AA* aa)
464     {
465         _source = aa;
466         collectPublications!(PublishedAA!AA);
467     }
468 
469     ~this()
470     {
471         destructEach(_keys, _content);
472         callInheritedDtor;
473     }
474 
475     /**
476      * Copies the content of an associative array to the internal containers.
477      *
478      * Typically called before serializing and if the instance is created
479      * using the default constructor.
480      *
481      * Params:
482      *      aa = The associative array to get.
483      */
484     void fromAA(ref AA aa)
485     {
486         Appender!(KeyType!AA[]) keyApp;
487         Appender!(ValueType!AA[]) valApp;
488 
489         keyApp.reserve(aa.length);
490         valApp.reserve(aa.length);
491 
492         foreach(kv; aa.byKeyValue)
493         {
494             keyApp.put(kv.key);
495             valApp.put(kv.value);
496         }
497 
498         _keys = keyApp.data;
499         _content = valApp.data;
500     }
501 
502     /**
503      * Resets then fills an associative array using the internal containers.
504      *
505      * Typically called after serializing and if the instance is created
506      * using the default constructor.
507      *
508      * Params:
509      *      aa = The associative array to set.
510      */
511     void toAA(ref AA aa)
512     {
513         aa = aa.init;
514         foreach(immutable i; 0 .. _keys.length)
515             aa[_keys[i]] = _content[i];
516     }
517 }
518 ///
519 unittest
520 {
521     uint[char] aa = ['c' : 0u, 'x' : 12u];
522     auto serializableAA = construct!(PublishedAA!(uint[char]));
523     serializableAA.fromAA(aa);
524     aa = aa.init;
525     version(none) publisherToFile(serializableAA, "backup.txt");
526     version(none) fileToPublisher("backup.txt", serializableAA);
527     serializableAA.toAA(aa);
528     assert(aa == ['c' : 0u, 'x' : 12u]);
529     destruct(serializableAA);
530 }
531 
532 unittest
533 {
534     alias AAT = uint[float];
535     alias AAC = PublishedAA!AAT;
536 
537     uint[float] a = [0.1f: 1u, 0.2f : 2u];
538     AAC aac = construct!AAC;
539     Serializer ser = construct!Serializer;
540     MemoryStream str = construct!MemoryStream;
541     scope(exit) destructEach(aac, ser, str);
542 
543     aac.fromAA(a);
544     ser.publisherToStream(aac, str);
545     str.position = 0;
546     a = a.init;
547     ser.streamToPublisher(str, aac);
548     aac.toAA(a);
549     assert(a == [0.1f: 1u, 0.2f : 2u]);
550 
551     str.clear;
552     AAC aac2 = construct!AAC(&a);
553     scope(exit) destruct(aac2);
554     ser.publisherToStream(aac2, str);
555     str.position = 0;
556     a = a.init;
557     ser.streamToPublisher(str, aac2);
558     assert(a == [0.1f: 1u, 0.2f : 2u]);
559 }
560 
561 
562 /**
563  * The Published2dArray class template allows to serialize 2-dimensional arrays.
564  *
565  * A Serializer is not able to directly handle them but this class
566  * does the task automatically by merging the array and storing an additional
567  * property for the dimensions.
568  *
569  * This class template can only be instantiated if $(I T) is a basic type.
570  * (see iz.types.BasicTypes).
571  *
572  * Params:
573  *      T = The array element type.
574  */
575 final class Published2dArray(T): PropertyPublisher
576 if (isBasicRtType!T)
577 {
578 
579     mixin inheritedDtor;
580     mixin PropertyPublisherImpl;
581 
582 protected:
583 
584     Array!uint _dimensions;
585     Array!T _content;
586     T[][]* _source;
587 
588     uint _setCount, _getCount;
589 
590     void doSet()
591     {
592         if (_setCount++ % 2 == 0 && _setCount > 0)
593             toArray(*_source);
594     }
595 
596     void doGet()
597     {
598         if (_getCount++ %  2 == 0)
599             fromArray(*_source);
600     }
601 
602     @Get uint[] dimensions()
603     {
604         if (_source) doGet;
605         return _dimensions[];
606     }
607 
608     @Set void dimensions(uint[] value)
609     {
610         _dimensions = value;
611         if (_source) doSet;
612     }
613 
614     @Get T[] content()
615     {
616         if (_source) doGet;
617         return _content[];
618     }
619 
620     @Set void content(T[] value)
621     {
622         _content = value;
623         if (_source) doSet;
624     }
625 
626 public:
627 
628     ///
629     this()
630     {
631         collectPublications!(Published2dArray!T);
632     }
633 
634     /**
635      * Constructs a new instance and sets a reference to the source array.
636      * Using this constructor, $(D toArray()) and $(D fromArray()) don't need
637      * to be called manually.
638      *
639      * Params:
640      *      array = A pointer to the array that will be stored and restored.
641      */
642     this(T[][]* array)
643     {
644         _source = array;
645         collectPublications!(Published2dArray!T);
646     }
647 
648     ~this()
649     {
650         destructEach(_dimensions, _content);
651         callInheritedDtor();
652     }
653 
654     /**
655      * Copies the content of the array to the internal container.
656      *
657      * Typically called before serializing and if the instance is created
658      * using the default constructor.
659      *
660      * Params:
661      *      array = The array to get.
662      */
663     void fromArray(ref T[][] array)
664     out
665     {
666         static if (__VERSION__ != 2076L)
667         {
668             import std.algorithm.iteration: reduce;
669             assert(_dimensions.reduce!((a,b) => a + b) <= uint.max);
670         }
671     }
672     body
673     {
674         _dimensions = _dimensions.init;
675         _content = _content.init;
676         foreach(immutable i; 0 .. array.length)
677         {
678             _dimensions ~= cast(uint)array[i].length;
679             _content ~= array[i];
680         }
681     }
682 
683     /**
684      * Resets then fills the array using the internal data.
685      *
686      * Typically called after serializing and if the instance is created
687      * using the default constructor.
688      *
689      * Params:
690      *      array = The array to set.
691      */
692     void toArray(ref T[][] array)
693     {
694         array = array.init;
695         array.length = _dimensions.length;
696         uint start;
697         size_t i;
698         foreach(len; _dimensions)
699         {
700             array[i].length = len;
701             array[i] = _content[][start .. start + len];
702             start += len;
703             ++i;
704         }
705     }
706 }
707 ///
708 unittest
709 {
710     alias Publisher = Published2dArray!uint;
711     Publisher pub = construct!Publisher;
712     scope(exit) destruct(pub);
713 
714     auto array = [[0u,1u],[2u,3u,4u],[5u,6u,7u,8u]];
715     pub.fromArray(array);
716     version (none) publisherToFile(pub, "backup.txt");
717     version (none) fileToPublisher("backup.txt", pub);
718     pub.toArray(array);
719     assert(array == [[0u,1u],[2u,3u,4u],[5u,6u,7u,8u]]);
720 }
721 
722 unittest
723 {
724     alias Publisher = Published2dArray!uint;
725     uint[][]src = [[0u,1u],[2u,3u,4u],[5u,6u,7u,8u]];
726 
727     Serializer ser = construct!Serializer;
728     MemoryStream str = construct!MemoryStream;
729     scope(exit) destructEach(ser, str);
730 
731     Publisher pub0 = construct!Publisher;
732     uint[][] array = src.dup;
733     pub0.fromArray(array);
734     assert(pub0._content == [0u,1u,2u,3u,4u,5u,6u,7u,8u]);
735     assert(pub0._dimensions == [2u,3u,4u]);
736     ser.publisherToStream(pub0, str);
737     array = array.init;
738     str.position = 0;
739     ser.streamToPublisher(str, pub0);
740     pub0.toArray(array);
741     assert(array == src);
742 
743     str.clear;
744     Publisher pub1 = construct!Publisher(&array);
745     ser.publisherToStream(pub1, str);
746     assert(pub1._content == [0u,1u,2u,3u,4u,5u,6u,7u,8u]);
747     assert(pub1._dimensions == [2u,3u,4u]);
748     array = array.init;
749     str.position = 0;
750     ser.streamToPublisher(str, pub1);
751     assert(array == src);
752 
753     destructEach(pub0, pub1);
754 }
755 
756 
757 
758 /// Enumerates the possible notifications sent to a ComponentObserver
759 enum ComponentNotification
760 {
761     /**
762      * The Component parameter of the notifySubject() is now owned.
763      * The owner that also matches the caller can be retrieved using
764      * the .owner() property of the parameter.
765      */
766     added,
767     /**
768      * The Component parameter of the notifySubject() is about to be destroyed,
769      * after what any of its reference that's been escaped will be danling.
770      * The parameter may match the emitter itself or one of its owned Component.
771      */
772     free,
773     /**
774      * The Component parameter of the notifySubject() is about to be serialized.
775      */
776     serialize,
777     /**
778      * The Component parameter of the notifySubject() is about to be deserialized.
779      */
780     deserialize,
781 }
782 
783 /**
784  * Defines the interface a class that wants to observe a Component has to implement.
785  * There is a single method: subjectNotification(ComponentNotification n, Component c)
786  */
787 alias ComponentObserver = EnumBasedObserver!(ComponentNotification, Component);
788 // matching emitter
789 private alias ComponentSubject = CustomSubject!(ComponentNotification, Component);
790 
791 /**
792  * Component is a high-level class that proposes an automatic memory
793  * managment model based on ownership. It also verifies the requirements to
794  * make its instances referencable and serializable.
795  *
796  * Ownership:
797  * A Component can be created with iz.memory.construct. As constructor parameter
798  * another Component can be specified. It's in charge for freeing this "owned"
799  * instance. Components that's not owned have to be freed manually. A reference
800  * to an owned object can be escaped. To be notified of its destruction, it's
801  * possible to observe the component or its owner by adding an observer to the
802  * componentSubject.
803  *
804  * Referencable:
805  * Each Component instance that's properly named is automatically registered
806  * in the ReferenceMan, as a void reference. This allows some powerfull features
807  * such as the Object property editor or the Serializer to inspect, store, retrieve
808  * a Component between two sessions.
809  *
810  * Serializable:
811  * A Component implements the PropertyPublisher interface. Each field annotated
812  * with @SetGet and each setter/getter pair annotated with @Set and @Get
813  * is automatically published and is usable by a PropertyBinder, by a Serializer
814  * or by any other system based on the PropDescriptor system.
815  */
816 class Component: PropertyPublisher
817 {
818 
819     mixin inheritedDtor;
820     mixin PropertyPublisherImpl;
821 
822 private:
823 
824     Component _owner;
825     DynamicList!Component _owned;
826     ComponentSubject _compSubj;
827 
828     void addOwned(Component o)
829     {
830         if (!o) return;
831         _owned.add(o);
832         foreach(obs; _compSubj.observers)
833             obs.subjectNotification(ComponentNotification.added, this);
834     }
835 
836 protected:
837 
838     Array!char _name;
839 
840 public:
841 
842     ///
843     this()
844     {
845         collectPublications!Component;
846         _compSubj = construct!ComponentSubject;
847         _owned = construct!(DynamicList!Component);
848     }
849 
850     /**
851      * Constructs a new instance whose life-time will be managed.
852      * Params:
853      *      owner = The Component that owns the new instance.
854      */
855     static C create(C = typeof(this))(Component owner)
856     if (is(C : Component))
857     {
858         C c = construct!C;
859         c._owner = owner;
860         if (owner) owner.addOwned(c);
861         return c;
862     }
863 
864     /**
865      * Destructs this and all the owned instances.
866      */
867     ~this()
868     {
869         ReferenceMan.removeReference(this);
870         foreach_reverse(o; _owned)
871         {
872             // observers can invalidate any escaped reference to a owned
873             foreach(obs; _compSubj.observers)
874                 obs.subjectNotification(ComponentNotification.free, o);
875             destruct(o);
876         }
877         // observers can invalidate any escaped reference to this instance
878         foreach(obs; _compSubj.observers)
879             obs.subjectNotification(ComponentNotification.free, this);
880         //
881         destruct(_name);
882         destruct(_compSubj);
883         destruct(_owned);
884         callInheritedDtor();
885     }
886 
887     /// Returns this instance onwer.
888     final const(Component) owner() {return _owner;}
889 
890     /// Returns the subject allowing ComponentObservers to observe this instance.
891     final ComponentSubject componentSubject() {return _compSubj;}
892 
893     // name things ------------------------------------------------------------+
894 
895     /// Returns true if a string is available as an unique Component name.
896     final bool nameAvailable(in char[] value)
897     {
898         if (_owner !is null && _owner._owned.first)
899         {
900             foreach(o; _owner._owned)
901                 if (o.name == value) return false;
902         }
903         return true;
904     }
905 
906     /// Suggests an unique Component name according to base.
907     final char[] getUniqueName(in char[] base)
908     {
909         import std.conv: to;
910         size_t i;
911         char[] result = base.dup;
912         while (!nameAvailable(result))
913         {
914             result = base ~ '_' ~ to!(char[])(i++);
915             if (i == size_t.max)
916                 return result.init;
917         }
918         return result;
919     }
920 
921     /**
922      * Defines the name of this Component.
923      *
924      * The name must be an unique value in the owner Component tree.
925      * This value is a published property.
926      * This value is stored as an ID in the ReferenceMan with the void type.
927      */
928     final @Set void name(const(char)[] value)
929     {
930         if (_name == value) return;
931         ReferenceMan.removeReference(this);
932         if (nameAvailable(value)) _name = value;
933         else _name = getUniqueName(value);
934         ReferenceMan.storeReference(this, qualifiedName);
935     }
936     /// ditto
937     final @Get const(char)[] name() {return _name[];}
938 
939     /**
940      * Returns the fully qualified name of this component in the owner
941      * Component tree.
942      */
943     final const(char)[] qualifiedName()
944     {
945         const(char)[][] result;
946         result ~= _name[];
947         Component c = _owner;
948         while (c)
949         {
950             result ~= c.name;
951             c = c._owner;
952         }
953         return result.retro.join(".");
954     }
955     // ----
956 }
957 
958 unittest
959 {
960 
961     Component root = Component.create(null);
962     root.name = "root";
963     assert(root.owner is null);
964     assert(root.name == "root");
965     assert(root.qualifiedName == "root");
966 
967     auto owned1 = Component.create!Component(root);
968     owned1.name = "component1".dup;
969     assert(owned1.owner is root);
970     assert(owned1.name == "component1");
971     assert(owned1.qualifiedName == "root.component1");
972 
973     Component owned11 = Component.create!Component(owned1);
974     owned11.name = "component1";
975     assert(owned11.owner is owned1);
976     assert(owned11.name == "component1");
977     assert(owned11.qualifiedName == "root.component1.component1");
978 
979     Component owned12 = Component.create!Component(owned1);
980     owned12.name = "component1";
981     assert(owned12.name == "component1_0");
982     assert(owned12.qualifiedName == "root.component1.component1_0");
983 
984     root.destruct;
985     // owned1, owned11 & owned12 are dangling but that's expected.
986     // Component instances are designed to be created and declared inside
987     // other Components. Escaped refs can be set to null using the Observer system.
988 }
989 
990 unittest
991 {
992     auto c = Component.create!Component(null);
993     c.name = "a";
994     assert(ReferenceMan.referenceID(cast(Component)c) == "a");
995     destruct(c);
996 }
997 
998 package class BaseTimer: PropertyPublisher
999 {
1000 
1001     mixin inheritedDtor;
1002     mixin PropertyPublisherImpl;
1003 
1004 protected:
1005 
1006     __gshared void delegate(Object) _onTimer;
1007     __gshared uint _interval = 1000;
1008     __gshared bool _active;
1009 
1010 public:
1011 
1012     ///
1013     this()
1014     {
1015         collectPublications!BaseTimer;
1016     }
1017 
1018     /// Starts the timer.
1019     void start(){}
1020 
1021     /// Stops the timer.
1022     void stop(){}
1023 
1024     /// Starts or stops the timer or indicates its status.
1025     @Set void active(bool value)
1026     {
1027         if (value == _active)
1028             return;
1029         _active = value;
1030         _active ? start : stop;
1031     }
1032 
1033     /// ditto
1034     @Get bool active(){return _active;}
1035 
1036     /**
1037      * Sets or gets the interval, in milliseconds, between each $(D onTimer) event.
1038      *
1039      * Accuracy is relative to the implementation.
1040      */
1041     @Set void interval(uint value)
1042     {
1043         _interval = (value > 10) ? value : 10;
1044     }
1045 
1046     /// ditto
1047     @Get uint interval() {return _interval;}
1048 
1049     /**
1050      * Sets or gets the event called when at least $(D interval) milliseconds
1051      * have ellapsed.
1052      */
1053     @Set void onTimer(void delegate(Object) value)
1054     {
1055         _onTimer = value;
1056     }
1057 
1058     /// ditto
1059     @Get void delegate(Object) onTimer() {return _onTimer;}
1060 }
1061 
1062 /**
1063  * A timer based on a thread.
1064  *
1065  * This timer ensures a minimal interval but not a periodicity.
1066  */
1067 class ThreadTimer: BaseTimer
1068 {
1069 
1070     mixin inheritedDtor;
1071 
1072 private:
1073 
1074     import core.atomic: atomicLoad, atomicStore;
1075 
1076     shared bool _stop;
1077     ulong _t1, _t2;
1078     Thread _thread;
1079 
1080     void execute()
1081     {
1082         while (true)
1083         {
1084             if (!_t1) _t1 = TickDuration.currSystemTick.msecs;
1085             _thread.sleep(dur!"msecs"(5));
1086             _t2 = TickDuration.currSystemTick.msecs;
1087             if (_t2 - _t1 > _interval)
1088             {
1089                 _t1 = 0;
1090                 if (_onTimer) _onTimer(this);
1091             }
1092             if (_stop.atomicLoad)
1093             {
1094                 break;
1095             }
1096         }
1097     }
1098 
1099 public:
1100 
1101     ~this()
1102     {
1103         stop();
1104         callInheritedDtor();
1105         destruct(_thread);
1106     }
1107 
1108     final override void start()
1109     {
1110         stop();
1111         _t1 = 0;
1112         _stop.atomicStore(false);
1113         if (_thread) destruct(_thread);
1114         _thread = construct!Thread(&execute);
1115         _thread.start;
1116     }
1117 
1118     final override void stop()
1119     {
1120         if (_thread)
1121         {
1122             _stop.atomicStore(true);
1123             if (_thread)
1124                 destruct(_thread);
1125         }
1126     }
1127 }
1128 
1129 //TODO:-cProcess: add option for std.process.Config.suppressConsole
1130 
1131 /**
1132  * Process encapsulates several useful methods of std.process
1133  * to make a serializable, synchronous process.
1134  */
1135 class Process: PropertyPublisher
1136 {
1137 
1138     mixin inheritedDtor;
1139     mixin PropertyPublisherImpl;
1140 
1141 private:
1142 
1143     Pid _pid;
1144     ProcessPipes _ppid;
1145     int _exitStatus;
1146 
1147     char[] env(bool full = false)()
1148     {
1149         import std.path: pathSeparator;
1150         import std.algorithm: each;
1151         import std..string: format;
1152         char[] result;
1153         static const string fmt = "%s=%s%s";
1154 
1155         static if (full)
1156         {
1157             auto aa = std.process.environment.toAA;
1158             aa.byKey.each!(
1159                 (k) => result ~= format(fmt, k, aa[k], pathSeparator));
1160         }
1161 
1162         _environment.byKey.each!(
1163             (k) => result ~= format(fmt, k, _environment[k], pathSeparator));
1164 
1165         return result;
1166     }
1167 
1168 protected:
1169 
1170     char[][] _parameters;
1171     string[string] _environment;
1172     char[] _executable;
1173     char[] _workingDirectory;
1174     bool _usePipes;
1175     bool _errorToOutput;
1176     bool _newEnvironment;
1177     bool _hideConsole;
1178     bool _isShellCommand;
1179 
1180     /// default, non blocking, execute
1181     final void internalExec()
1182     {
1183         Redirect r;
1184         with (Redirect) if (_usePipes)
1185         {
1186             if (!_errorToOutput) r = Redirect.all;
1187             else r = stdin | stdout | stderrToStdout;
1188         }
1189         Config c;
1190         if (_newEnvironment)
1191             c = Config.newEnv;
1192         if (_hideConsole)
1193             c |= Config.suppressConsole;
1194         if (!_isShellCommand)
1195             _ppid = pipeProcess([_executable] ~ _parameters,
1196                 r, _environment, c, _workingDirectory);
1197         else
1198             _ppid = pipeShell(_executable ~ " " ~ _parameters.join(' '),
1199                 r, _environment, c, _workingDirectory);
1200     }
1201 
1202 public:
1203 
1204     ///
1205     this()
1206     {
1207         collectPublications!Process;
1208     }
1209 
1210     /**
1211      * Executes the process and returns when it terminates.
1212      */
1213     void execute()
1214     {
1215         internalExec;
1216         waitFor;
1217     }
1218 
1219     /**
1220      * Executes then writes and finally closes the input stream.
1221      *
1222      * This is useful for processes that directly expects
1223      * an input after being launched.
1224      */
1225     void executeAndWrite(T)(auto ref T t)
1226     {
1227         execute;
1228         input.write(t);
1229         input.close;
1230     }
1231 
1232     /**
1233      * Executes, fills the input stream with a file and finally
1234      * closes the input.
1235      */
1236     void executeAndPipeFile(const(char[]) filename)
1237     {
1238         import std.file: read;
1239         auto buff = read(filename);
1240         executeAndWrite(buff);
1241     }
1242 
1243     /**
1244      * Forces the process termination.
1245      *
1246      * Params:
1247      *      status = the exit status.
1248      */
1249     void terminate(int status = 0)
1250     {
1251         kill(_ppid.pid, status);
1252     }
1253 
1254     /**
1255      * Sets or gets if the process streams are redirected.
1256      */
1257     @Set void usePipes(bool value)
1258     {
1259         _usePipes = value;
1260     }
1261 
1262     /// ditto
1263     @Get bool usePipes() {return _usePipes;}
1264 
1265     /**
1266      * Sets or gets if the error stream is redirected to the output stream.
1267      * Only applied if usePipes is set to true.
1268      */
1269     @Set void errorToOutput(bool value)
1270     {
1271         _errorToOutput = value;
1272     }
1273 
1274     /// ditto
1275     @Get bool errorToOutput() {return _usePipes;}
1276 
1277     /**
1278      * Sets or gets if the value passed in environment fully replaces
1279      * the default environment.
1280      */
1281     @Set void newEnvironment(bool value)
1282     {
1283         _newEnvironment = value;
1284     }
1285 
1286     /// ditto
1287     @Get bool newEnvironment() {return _newEnvironment;}
1288 
1289     /**
1290      * Sets or gets the process executable.
1291      */
1292     @Set void executable(const(char)[] value)
1293     {
1294         _executable = value.dup;
1295     }
1296 
1297     /// ditto
1298     @Get const(char)[] executable()
1299     {
1300         return _executable;
1301     }
1302 
1303     /**
1304      * Sets or gets the process working directory.
1305      */
1306     @Set void workingDirectory(const(char)[] value)
1307     {
1308         _workingDirectory = value.dup;
1309     }
1310 
1311     /// ditto
1312     @Get const(char)[] workingDirectory()
1313     {
1314         return _workingDirectory;
1315     }
1316 
1317     /**
1318      * The environment, as an associative array.
1319      */
1320     string[string] environmentAA()
1321     {
1322         return _environment;
1323     }
1324 
1325     /**
1326      * Sets or gets the environment.
1327      *
1328      * The value returned is changes according to $(D newEnvironment) property.
1329      *
1330      * Params:
1331      *      value = a string containing the environment variables,
1332      *      each group separated by the system's pathSeparator and in a group,
1333      *      the name separated of its value(s) by the equal symbol.
1334      */
1335     @Set void environment(const(char)[] value)
1336     {
1337         import std.path: pathSeparator;
1338         string v, k;
1339         char[] item;
1340         char[] valuecopy = value.dup;
1341         auto items = valuecopy.bySeparated(pathSeparator);
1342         while (true)
1343         {
1344             if (items.empty) break;
1345             item = items.front;
1346             auto kv = item.bySeparated('=');
1347             if (kv.empty) continue;
1348             k = kv.front.idup;
1349             kv.popFront;
1350             if (kv.empty) continue;
1351             v = kv.front.idup;
1352             items.popFront();
1353             _environment[k] = v;
1354         }
1355     }
1356 
1357     /// ditto
1358     @Get const(char)[] environment()
1359     {
1360         return env();
1361     }
1362 
1363     /**
1364      * Returns the full environment.
1365      *
1366      * When newEnvironment is set to true, the same value as environement
1367      * is returned.
1368      */
1369     string fullEnvironment()
1370     {
1371         if (_newEnvironment)
1372             return env().idup;
1373         else
1374             return env!true().idup;
1375     }
1376 
1377     /**
1378      * Sets or gets the parameters for this process.
1379      *
1380      * Params:
1381      *      value = a string containing the parameters, separated by ascii white
1382      *      characters.
1383      */
1384     @Set void parameters(string value)
1385     {
1386         _parameters.length = 0;
1387         foreach(p; value.byWord)
1388             _parameters ~= p;
1389     }
1390 
1391     /// ditto
1392     @Get string parameters()
1393     {
1394         import std.array: join;
1395         return join(_parameters, ' ').idup;
1396     }
1397 
1398     /**
1399      * Sets or gets if the console of the program
1400      * is visible.
1401      */
1402     @Set void hideConsole(bool value)
1403     {
1404         _hideConsole = value;
1405     }
1406 
1407     /// ditto
1408     @Get bool hideConsole() {return _hideConsole;}
1409 
1410     /**
1411      * Sets or gets if the $(D executable) field actually
1412      * represents a shell command.
1413      */
1414     @Set void isShellCommand(bool value)
1415     {
1416          _isShellCommand = value;
1417     }
1418 
1419     /// ditto
1420     @Get bool isShellCommand() {return _isShellCommand;}
1421 
1422     /**
1423      * Waits for the process and only returns when it terminates.
1424      */
1425     final int waitFor()
1426     {
1427         _exitStatus = wait(_ppid.pid);
1428         return _exitStatus;
1429     }
1430 
1431     /**
1432      * Indicates wether the process is terminated.
1433      */
1434     final bool terminated()
1435     {
1436         return tryWait(_ppid.pid)[0];
1437     }
1438 
1439     /**
1440      * Indicates the process exit status.
1441      *
1442      * Only reliable when terminated is true.
1443      */
1444     final int exitStatus()
1445     {
1446         _exitStatus = tryWait(_ppid.pid)[1];
1447         return _exitStatus;
1448     }
1449 
1450     /**
1451      * The process input stream.
1452      *
1453      * Returns:
1454      *      std.stdio.stdin when usePipes is set to false, otherwise another
1455      *      stream.
1456      */
1457     final File input()
1458     {
1459         return _ppid.stdin;
1460     }
1461 
1462     /**
1463      * The process output stream.
1464      *
1465      * Returns:
1466      *      std.stdio.stdout when usePipes is set to false, otherwise another
1467      *      stream.
1468      */
1469     final File output()
1470     {
1471         return _ppid.stdout;
1472     }
1473 
1474     /**
1475      * The process error stream.
1476      *
1477      * Returns:
1478      *      std.stdio.stderr when usePipes is set to false, otherwise another
1479      *      stream, the same as output if errorToOutput is set to true.
1480      */
1481     final File error()
1482     {
1483         return _ppid.stderr;
1484     }
1485 }
1486 ///
1487 version(Posix) unittest
1488 {
1489     Process prc = construct!Process;
1490     scope(exit) prc.destruct;
1491 
1492     prc.executable = "echo";
1493     prc.parameters = "123";
1494     prc.usePipes = true;
1495     prc.execute;
1496     assert(prc.output.readln == "123\n");
1497 }
1498 
1499 unittest
1500 {
1501 
1502     import std.file: write, exists, tempDir, getcwd, remove;
1503     import std.path: dirSeparator;
1504 
1505     auto code =
1506     q{
1507         import std.stdio;
1508         void main()
1509         {
1510             write("hello world");
1511         }
1512     };
1513 
1514     version(Windows) enum exeExt = ".exe"; else enum exeExt = "";
1515     version(Windows) enum objExt = ".obj"; else enum objExt = ".o";
1516 
1517     string fname = getcwd ~ dirSeparator ~ "TempSource";
1518     string srcName = fname ~ ".d";
1519     string exeName = fname ~ exeExt;
1520     string objName = fname ~ objExt;
1521     scope(exit)
1522     {
1523         remove(srcName);
1524         remove(exeName);
1525         remove(objName);
1526     }
1527 
1528     write(srcName, code);
1529 
1530     // compiles code with dmd
1531     Process dmdProc = construct!Process;
1532     dmdProc.executable = "dmd";
1533     dmdProc.parameters = srcName;
1534     dmdProc.execute;
1535     assert(dmdProc.terminated);
1536     assert(dmdProc.exitStatus == 0);
1537     assert(exeName.exists);
1538 
1539     // run produced program
1540     Process runProc = construct!Process;
1541     runProc.executable = exeName;
1542     runProc.usePipes = true;
1543     runProc.execute;
1544     assert(runProc.terminated);
1545     assert(runProc.exitStatus == 0);
1546     assert(runProc.output.readln == "hello world");
1547 
1548     destructEach(runProc, dmdProc);
1549 }
1550 
1551 /**
1552  * AsyncProcess is a non blocking process that exposes two assignable
1553  * events allowing to be notified when the process output has changed
1554  * or when the process has terminated.
1555  *
1556  * This class relies on an internal ThreadTimer that could not work in all
1557  * the contexts (for example in a X window).
1558  */
1559 class AsyncProcess: Process
1560 {
1561 
1562     mixin inheritedDtor;
1563     mixin PropertyPublisherImpl;
1564 
1565 private:
1566 
1567     ThreadTimer _checker;
1568     void delegate(Object notifier) _onTerminate, _onOutputBuffer;
1569 
1570 protected:
1571 
1572     void check(Object notifier)
1573     {
1574         version(Posix)
1575         {
1576             if (terminated || _ppid.stdout.eof)
1577             {
1578                 _checker.stop;
1579                 if (_onTerminate)
1580                     _onTerminate(this);
1581             }
1582             else
1583             {
1584                 pollfd pfd = { _ppid.stdout.fileno, POLLIN };
1585                 if (_onOutputBuffer && poll(&pfd, 1, 0) && (pfd.revents & POLLIN))
1586                     _onOutputBuffer(this);
1587             }
1588         }
1589         else 
1590         {
1591             import core.sys.windows.winbase : WaitForSingleObject;
1592             if (_onOutputBuffer && WaitForSingleObject(_ppid.stdout.windowsHandle, 0))
1593                 _onOutputBuffer(this);           
1594         }   
1595     }
1596 
1597 public:
1598 
1599     ///
1600     this()
1601     {
1602         collectPublications!AsyncProcess;
1603         _checker = construct!ThreadTimer;
1604         _checker.onTimer = &check;
1605         _checker.interval = 20;
1606     }
1607 
1608     ~this()
1609     {
1610         destruct(_checker);
1611         callInheritedDtor();
1612     }
1613 
1614     /**
1615      * Executes and returns immediatly.
1616      *
1617      * The process termination can be detected with the terminated property
1618      * or with the onTerminate event.
1619      */
1620     override void execute()
1621     {
1622         _checker.start;
1623         internalExec;
1624     }
1625 
1626     /**
1627      * Reads from the output buffer
1628      *
1629      * Convenience function that can be called in the two async events.
1630      *
1631      * Params:
1632      *      t = An array to overwrite. Its size defines the max amount of data
1633      *      to read.
1634      *
1635      * Returns:
1636      *      A bool that indicates if something has been read.
1637      */
1638     bool readOutput(T)(ref T[] t)
1639     {
1640         version(Posix)
1641         {
1642             import core.sys.posix.unistd: read;
1643             auto c = read(output.fileno, t.ptr, t.length * T.sizeof);
1644             t.length = c / T.sizeof;
1645             return c != 0;
1646         }
1647         else
1648         {
1649             import core.sys.windows.winbase: ReadFile;
1650             import core.sys.windows.winnt: LARGE_INTEGER;
1651             uint c;
1652             LARGE_INTEGER li;
1653             li.QuadPart = t.length;
1654             ReadFile(output.windowsHandle , t.ptr, Li.LowPart, &c, null);
1655             t.length = c / T.sizeof;
1656             return c != 0;
1657         }
1658     }
1659 
1660     /**
1661      * Sets or gets the event called when the process terminates.
1662      */
1663     @Set void onTerminate(void delegate(Object) value)
1664     {
1665         _onTerminate = value;
1666     }
1667 
1668     /// ditto
1669     @Get void delegate(Object) onTerminate() {return _onTerminate;}
1670 
1671     /**
1672      * Sets or gets the event called when the process has availalbe output.
1673      */
1674     @Set void onOutputBuffer(void delegate(Object) value)
1675     {
1676         _onOutputBuffer = value;
1677     }
1678 
1679     /// ditto
1680     @Get void delegate(Object) onOutputBuffer() {return _onOutputBuffer;}
1681 }
1682 ///
1683 version(Posix) version(none)  unittest
1684 {
1685     import std.process: environment;
1686     if (environment.get("TRAVIS") == "true")
1687         return;
1688 
1689     import std.file: write, exists, tempDir, getcwd, remove;
1690     import std.path: dirSeparator;
1691 
1692     auto code =
1693     q{
1694         import std.stdio;
1695         import core.thread;
1696         void main()
1697         {
1698             write("hello world");
1699             stdout.flush;
1700             Thread.sleep(dur!"msecs"(15));
1701             Thread.sleep(dur!"msecs"(15));
1702         }
1703     };
1704 
1705     version(Windows) enum exeExt = ".exe"; else enum exeExt = "";
1706     version(Windows) enum objExt = ".obj"; else enum objExt = ".o";
1707 
1708     string fname = getcwd ~ dirSeparator ~ "TempSource";
1709     string srcName = fname ~ ".d";
1710     string exeName = fname ~ exeExt;
1711     string objName = fname ~ objExt;
1712     scope(exit)
1713     {
1714         remove(srcName);
1715         remove(exeName);
1716         remove(objName);
1717     }
1718 
1719     write(srcName, code);
1720 
1721     // compiles code with dmd
1722     Process dmdProc = construct!Process;
1723     dmdProc.executable = "dmd";
1724     dmdProc.parameters = srcName;
1725     dmdProc.execute;
1726     assert(dmdProc.terminated);
1727     assert(dmdProc.exitStatus == 0);
1728     assert(exeName.exists);
1729 
1730     // aggregate to catch the events
1731     struct Catcher
1732     {
1733         bool ter;
1734         bool flg;
1735         void bufferAvailable(Object notifier)
1736         {
1737             flg = true;
1738             AsyncProcess proc = cast(AsyncProcess) notifier;
1739             //TODO-cleaks: this readln creates a leak
1740             string s = proc.output.readln;
1741             assert(s == "hello world");
1742         }
1743         void terminate(Object notifier)
1744         {
1745             ter = true;
1746         }
1747     }
1748     Catcher catcher;
1749 
1750     // run produced program
1751     AsyncProcess runProc = construct!AsyncProcess;
1752     runProc.executable = exeName;
1753     runProc.usePipes = true;
1754     runProc.onOutputBuffer = &catcher.bufferAvailable;
1755     runProc.onTerminate = &catcher.terminate;
1756     assert(runProc.onOutputBuffer);
1757     assert(runProc.onTerminate);
1758     runProc.execute;
1759     import core.thread;
1760     while (!catcher.ter) {Thread.sleep(dur!"msecs"(10));}
1761     assert(runProc.terminated);
1762     assert(runProc.exitStatus == 0);
1763     assert(catcher.ter);
1764     assert(catcher.flg);
1765 
1766     destructEach(runProc, dmdProc);
1767 }
1768