1 /**
2  * The iz implementation of the observer pattern.
3  */
4 module iz.observer;
5 
6 import
7     iz.types, iz.memory, iz.containers;
8 
9 /**
10  * Subject (one to many) interface.
11  */
12 interface Subject
13 {
14     /// Determines if observer is suitable for this subject.
15     bool acceptObserver(Object observer);
16     /// An observer can be added after acceptObserver
17     void addObserver(Object observer);
18     /// an observer wants to be removed.
19     void removeObserver(Object observer);
20 }
21 
22 /**
23  * CustomSubject handles a list of obsever.
24  * Params:
25  * OT = the observer type, either an interface or a class.
26  */
27 class CustomSubject(OT): Subject
28 if (is(OT == interface) || is(OT == class))
29 {
30 
31 protected:
32 
33     DynamicList!OT _observers;
34 
35 public:
36 
37     ///
38     this()
39     {
40         _observers = construct!(DynamicList!OT);
41     }
42 
43     ~this()
44     {
45         destruct(_observers);
46     }
47 
48     /// see the Subject interface.
49     bool acceptObserver(Object observer)
50     {
51         return (cast(OT) observer !is null);
52     }
53 
54     /// see the Subject interface.
55     void addObserver(Object observer)
56     {
57         if (!acceptObserver(observer))
58             return;
59         OT obs = cast(OT) observer;
60         if (_observers.find(obs) != -1)
61             return;
62         _observers.add(obs);
63     }
64 
65     /// Calls addObserver() foreach object passed as argument.
66     void addObservers(Objs...)(Objs objs)
67     {
68         foreach(obj; objs)
69             addObserver(obj);
70     }
71 
72     /// see the Subject interface.
73     void removeObserver(Object observer)
74     {
75         if (auto obs = cast(OT) observer)
76             _observers.remove(obs);
77     }
78 
79     /// list of observers
80     DynamicList!OT observers()
81     {
82         return _observers;
83     }
84 }
85 
86 /**
87  * ObserverInterconnector is in charge for inter-connecting the subjects with
88  * their observers, whatever are their specializations.
89  *
90  * With this class, an observer can connect to the right subjects
91  * without having to know them. In the same fashion the subjects gets their
92  * client list automatically filled.
93  */
94 class ObserverInterconnector
95 {
96 
97 private:
98 
99     DynamicList!Object _observers;
100     DynamicList!Object _subjects;
101     ptrdiff_t fUpdateCount;
102 
103 public:
104 
105     ///
106     this()
107     {
108         _observers = construct!(DynamicList!Object);
109         _subjects = construct!(DynamicList!Object);
110     }
111 
112     ~this()
113     {
114         _observers.destruct;
115         _subjects.destruct;
116     }
117 
118     /**
119      * Several entities will be added.
120      * This Avoids any superfluous update while adding.
121      * Every beginUpdate() must be followed by an endUpdate().
122      */
123     void beginUpdate()
124     {
125         ++fUpdateCount;
126     }
127 
128     /**
129      * Several subjects or observers have been added.
130      * Decrements a counter and update the entities if it's equal to 0.
131      */
132     void endUpdate()
133     {
134         --fUpdateCount;
135         if (fUpdateCount > 0)
136             return;
137         updateAll;
138     }
139 
140     /**
141      * Adds an observer to the list.
142      */
143     void addObserver(Object observer)
144     {
145         if (_observers.find(observer) != -1)
146             return;
147         beginUpdate;
148         _observers.add(observer);
149         endUpdate;
150     }
151 
152     /**
153      * Adds a list of observer to the list.
154      * Optimized for bulk adding.
155      */
156     void addObservers(Objs...)(Objs objs)
157     {
158         beginUpdate;
159         foreach(obj; objs)
160             addObserver(obj);
161         endUpdate;
162     }
163 
164     /**
165      * Removes an observer from the list.
166      */
167     void removeObserver(Object observer)
168     {
169         beginUpdate;
170         _observers.remove(observer);
171         foreach(immutable i; 0 .. _subjects.count)
172             (cast(Subject) _subjects[i]).removeObserver(observer);
173         endUpdate;
174     }
175 
176     /**
177      * Adds a subject to the list.
178      */
179     void addSubject(Object subject)
180     {
181         if (_subjects.find(subject) != -1)
182             return;
183         if( (cast(Subject) subject) is null)
184             return;
185         beginUpdate;
186         _subjects.add(subject);
187         endUpdate;
188     }
189 
190     /**
191      * Adds several subjects to the list.
192      * Optimized for bulk addition.
193      */
194     void addSubjects(Subjs...)(Subjs subjs)
195     {
196         beginUpdate;
197         foreach(subj; subjs)
198             addSubject(subj);
199         endUpdate;
200     }
201 
202     /**
203      * Removes a subject from the entity list.
204      */
205     void removeSubject(Object subject)
206     {
207         beginUpdate;
208         _subjects.remove(subject);
209         endUpdate;
210     }
211 
212     /**
213      * Updates the connections between the entities stored in the global list.
214      *
215      * It has usually not be called manually.
216      * During the process, each subject is visited by each observer.
217      *
218      * The complexity of the operation is usually reduced if beginUpdate()
219      * and endUpdate() are used adequatly.
220      */
221     void updateObservers()
222     {
223         fUpdateCount = 0;
224         foreach(immutable subjectIx; 0 .. _subjects.count)
225         {
226             Subject subject = cast(Subject) _subjects[subjectIx];
227             foreach(immutable observerIx; 0 .. _observers.count)
228                 subject.addObserver(_observers[observerIx]);
229         }
230     }
231 
232     /// ditto
233     alias updateAll = updateObservers;
234 
235 }
236 
237 unittest
238 {
239     interface PropObserver(T)
240     {
241         void min(T aValue);
242         void max(T aValue);
243         void def(T aValue);
244     }
245     alias IntPropObserver = PropObserver!int;
246 
247     class Foo: IntPropObserver
248     {
249         int _min, _max, _def;
250         void min(int aValue){_min = aValue;}
251         void max(int aValue){_max = aValue;}
252         void def(int aValue){_def = aValue;}
253     }
254     alias UintPropObserver = PropObserver!uint;
255 
256     class Bar: UintPropObserver
257     {
258         uint _min, _max, _def;
259         void min(uint aValue){_min = aValue;}
260         void max(uint aValue){_max = aValue;}
261         void def(uint aValue){_def = aValue;}
262     }
263 
264     class IntPropSubject : CustomSubject!IntPropObserver
265     {
266         int _min = int.min;
267         int _max = int.max;
268         int _def = int.init;
269         void updateObservers()
270         {
271             for(auto i = 0; i < _observers.count; i++)
272                 (cast(IntPropObserver)_observers[i]).min(_min);
273             for(auto i = 0; i < _observers.count; i++)
274                 (cast(IntPropObserver)_observers[i]).max(_max);
275             for(auto i = 0; i < _observers.count; i++)
276                 (cast(IntPropObserver)_observers[i]).def(_def);
277         }
278     }
279 
280     class UintPropSubject : CustomSubject!UintPropObserver
281     {
282         uint _min = uint.min;
283         uint _max = uint.max;
284         uint _def = uint.init;
285         void updateObservers()
286         {
287             for(auto i = 0; i < _observers.count; i++)
288                 (cast(UintPropObserver)_observers[i]).min(_min);
289             for(auto i = 0; i < _observers.count; i++)
290                 (cast(UintPropObserver)_observers[i]).max(_max);
291             for(auto i = 0; i < _observers.count; i++)
292                 (cast(UintPropObserver)_observers[i]).def(_def);
293         }
294     }
295 
296     auto nots1 = construct!Object;
297     auto nots2 = construct!Object;
298     auto inter = construct!ObserverInterconnector;
299     auto isubj = construct!IntPropSubject;
300     auto iobs1 = construct!Foo;
301     auto iobs2 = construct!Foo;
302     auto iobs3 = construct!Foo;
303     auto usubj = construct!UintPropSubject;
304     auto uobs1 = construct!Bar;
305     auto uobs2 = construct!Bar;
306     auto uobs3 = construct!Bar;
307 
308     scope(exit)
309     {
310         destructEach(inter, isubj, usubj);
311         destructEach(iobs1, iobs2, iobs3);
312         destructEach(uobs1, uobs2, uobs3);
313         destructEach(nots1, nots2);
314     }
315 
316     inter.beginUpdate;
317     // add valid entities
318     inter.addSubjects(isubj, usubj);
319     inter.addObservers(iobs1, iobs2, iobs3);
320     inter.addObservers(uobs1, uobs2, uobs3);
321     // add invalid entities
322     inter.addSubjects(nots1, nots2);
323     inter.addObservers(nots1, nots2);
324     // not added twice
325     inter.addSubjects(isubj, usubj);
326     inter.endUpdate;
327 
328     // check the subject and observers count
329     assert(inter._subjects.count == 2);
330     assert(inter._observers.count == 8);
331     assert(isubj.observers.count == 3);
332     assert(usubj.observers.count == 3);
333 
334     inter.beginUpdate;
335     inter.removeObserver(iobs1);
336     inter.endUpdate;
337 
338     assert(inter._subjects.count == 2);
339     assert(inter._observers.count == 7);
340     assert(isubj._observers.count == 2);
341     assert(usubj._observers.count == 3);
342 
343     // update subject
344     isubj._min = -127;
345     isubj._max = 128;
346     isubj.updateObservers;
347     // iobs1 has been removed
348     assert(iobs1._min != -127);
349     assert(iobs1._max != 128);
350     // check observers
351     assert(iobs2._min == -127);
352     assert(iobs2._max == 128);
353     assert(iobs3._min == -127);
354     assert(iobs3._max == 128);
355 
356     // update subject
357     usubj._min = 2;
358     usubj._max = 256;
359     usubj.updateObservers;
360     // check observers
361     assert(uobs1._min == 2);
362     assert(uobs1._max == 256);
363     assert(uobs2._min == 2);
364     assert(uobs2._max == 256);
365     assert(uobs3._min == 2);
366     assert(uobs3._max == 256);
367 }
368 
369 
370 
371 /**
372  * interface for an observer based on an enum.
373  * Params:
374  * E = an enum.
375  * T = variadic type of the parameters an observer monitors.
376  */
377 interface EnumBasedObserver(E, T...)
378 if (is(E == enum))
379 {
380     /**
381      * The method called by a subject.
382      * Params:
383      * notification = the E member allowing to distinguish the "call reason".
384      * t = the parameters the observer is interested by.
385      */
386     void subjectNotification(E notification, T t);   
387 }
388 
389 /**
390  * CustomSubject handles a list of obsever.
391  * This version only accept an observer if it's an EnumBasedObserver.
392  * Params:
393  * E = an enum.
394  * T = the variadic list of parameter types used in the notification. 
395  */
396 class CustomSubject(E, T...) : Subject 
397 if (is(E == enum))
398 {
399 
400 protected:
401 
402     alias ObserverType = EnumBasedObserver!(E,T);
403     DynamicList!ObserverType _observers;
404 
405 public:
406 
407     ///
408     this()
409     {
410         _observers = construct!(DynamicList!ObserverType);
411     }
412 
413     ~this()
414     {
415         _observers.destruct;
416     }
417 
418     /// see the Subject interface.
419     bool acceptObserver(Object observer)
420     in
421     {
422         assert(observer);
423     }
424     body
425     {
426         return (cast(ObserverType) observer !is null);
427     }
428 
429     /// see the Subject interface.
430     void addObserver(Object observer)
431     in
432     {
433         assert(observer);
434     }
435     body
436     {
437         if (!acceptObserver(observer))
438             return;
439         auto obs = cast(ObserverType) observer;
440         if (_observers.find(obs) != -1)
441             return;
442         _observers.add(obs);
443     }
444 
445     /// Calls addObserver() foreach object passed as argument.
446     void addObservers(Objs...)(Objs objs)
447     {
448         foreach(obj; objs)
449             addObserver(obj);
450     }
451 
452     /// see the Subject interface.
453     void removeObserver(Object observer)
454     in
455     {
456         assert(observer);
457     }
458     body
459     {
460         auto obs = cast(ObserverType) observer;
461         _observers.remove(obs);
462     }
463     ///
464     DynamicList!ObserverType observers()
465     {
466         return _observers;
467     }
468 } 
469 
470 unittest
471 {
472     enum DocumentNotification{opening, closing, saving, changed}
473     
474     class Document {}
475     class Highlighter {}
476     class Invalid {}
477     
478     class DocSubject : CustomSubject!(DocumentNotification, Document, Highlighter)
479     {
480         void notify(DocumentNotification dn)
481         {
482             foreach(obs;_observers)
483                 (cast(ObserverType) obs)
484                     .subjectNotification(dn, null, null);
485         }
486     }
487     class DocObserver: EnumBasedObserver!(DocumentNotification, Document, Highlighter)
488     {
489         DocumentNotification lastNotification;
490         void subjectNotification(DocumentNotification notification, Document doc, Highlighter hl)
491         {
492             lastNotification = notification;
493         }
494     }   
495     
496     ObserverInterconnector inter = construct!ObserverInterconnector;
497     DocSubject subj = construct!DocSubject;
498     DocObserver obs1 = construct!DocObserver;
499     DocObserver obs2 = construct!DocObserver;
500     DocObserver obs3 = construct!DocObserver;
501     
502     scope(exit) destructEach(inter, subj, obs1, obs2, obs3);
503     
504     inter.addSubject(subj);
505     inter.addObservers(obs1, obs2, obs3);
506     inter.addObservers(obs1, obs2, obs3);
507     assert(inter._observers.count == 3);
508     assert(subj._observers.count == 3);
509     
510     subj.notify(DocumentNotification.changed);
511     assert(obs1.lastNotification == DocumentNotification.changed);
512     assert(obs2.lastNotification == DocumentNotification.changed);
513     assert(obs3.lastNotification == DocumentNotification.changed);
514     subj.notify(DocumentNotification.opening);
515     assert(obs1.lastNotification == DocumentNotification.opening);
516     assert(obs2.lastNotification == DocumentNotification.opening);
517     assert(obs3.lastNotification == DocumentNotification.opening);
518 
519     inter.removeSubject(subj);
520     assert(inter._subjects.count == 0);
521     assert(inter._observers.count == 3);
522     assert(subj.observers.count == 3);
523 
524     inter.removeObserver(obs2);
525     assert(inter._observers.count == 2);
526     assert(subj.observers.count == 3);
527     subj.removeObserver(obs2);
528     assert(subj.observers.count == 2);
529 
530     auto inv = construct!Invalid;
531     inter.addSubject(subj);
532     inter.addObserver(inv);
533     assert(inter._observers.count == 3);
534     assert(subj.observers.count == 2);
535     destruct(inv);
536 }
537