1 /+
2 The MIT License (MIT)
3 
4     Copyright (c) <2013> <Oleg Butko (deviator), Anton Akzhigitov (Akzwar)>
5 
6     Permission is hereby granted, free of charge, to any person obtaining a copy
7     of this software and associated documentation files (the "Software"), to deal
8     in the Software without restriction, including without limitation the rights
9     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10     copies of the Software, and to permit persons to whom the Software is
11     furnished to do so, subject to the following conditions:
12 
13     The above copyright notice and this permission notice shall be included in
14     all copies or substantial portions of the Software.
15 
16     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22     THE SOFTWARE.
23 +/
24 
25 module des.util.arch.tree;
26 
27 import std.traits;
28 import std.string;
29 import std.algorithm;
30 import std.exception;
31 
32 import des.util.testsuite;
33 
34 ///
35 class TNodeException : Exception
36 {
37     this( string msg, string file=__FILE__, size_t line=__LINE__ )
38     { super( msg, file, line ); }
39 }
40 
41 ///
42 class TNodeCycleException : TNodeException
43 {
44     this( string file=__FILE__, size_t lien=__LINE__ )
45     { super( "cycle detect", file, line ); }
46 }
47 
48 ///
49 class TNodeNullChildException : TNodeException
50 {
51     this( string file=__FILE__, size_t lien=__LINE__ )
52     { super( "can't append null child", file, line ); }
53 }
54 
55 private
56 {
57     pure string replaceWords(alias fun)( string s )
58     {
59         string ret;
60         string buf;
61         size_t p0 = 0, p1 = 0;
62         while( p1 < s.length )
63         {
64             if( s[p1] == '%' )
65             {
66                 ret ~= s[p0..p1];
67                 p0 = ++p1;
68                 while( p1 < s.length && identitySymbol(s[p1]) )
69                     p1++;
70                 ret ~= fun(s[p0..p1]);
71                 p0 = p1;
72             }
73             else if( p1 == s.length - 1 )
74                 ret ~= s[p0..p1];
75             p1++;
76         }
77         return ret;
78     }
79 
80     pure bool identitySymbol( char c )
81     {
82         switch(c)
83         {
84             case 'a': .. case 'z': case 'A': .. case 'Z':
85             case '_': case '0': .. case '9': return true;
86             default: return false;
87         }
88     }
89 
90     pure bool identityString( string s )
91     {
92         foreach( c; s )
93             if( !identitySymbol(c) ) return false;
94         return true;
95     }
96 
97     template wordWrapFunc(string pre, string suf)
98     {
99         import std.exception;
100         static assert( identityString(pre) );
101         static assert( identityString(suf) );
102         string wordWrapFunc( string a )
103         {
104             enforce( a.length );
105             if( pre.length && pre[$-1] != '_' )
106                 a = (""~a[0]).toUpper ~ a[1..$];
107             return pre ~ a ~ suf;
108         }
109     }
110 }
111 
112 ///
113 template TNode(T,string prefix="",string suffix="")
114 {
115 interface TNode
116 {
117     mixin( replaceWords!(wordWrapFunc!(prefix,suffix))(q{
118     public
119     {
120         alias T %NodeType;
121 
122         @property
123         {
124             %NodeType %parent();
125             const(%NodeType) %parent() const;
126             %NodeType %parent( %NodeType p );
127 
128             %NodeType[] %childs();
129             const(%NodeType[]) %childs() const;
130         }
131 
132         final bool %findInChilds( const(%NodeType) obj ) const
133         {
134             foreach( ch; %childs )
135             {
136                 if( obj == ch ) return true;
137                 if( ch.%findInChilds(obj) ) return true;
138             }
139             return false;
140         }
141 
142         void %attachChilds( %NodeType[] att... );
143         void %detachChilds( %NodeType[] att... );
144     }
145 
146     protected
147     {
148         void %attachCallback( %NodeType[] att );
149         void %detachCallback( %NodeType[] det );
150     }
151 
152     void %setParent( %NodeType p );
153 
154     mixin template %TNodeHelper(bool __%with_empty_callback=true,
155                                 bool __%release_cycle_check=false,
156                                 bool __%release_nullchild_check=false )
157     {
158         protected
159         {
160             static if( __%with_empty_callback )
161             {
162                 void %attachCallback( %NodeType[] att ){}
163                 void %detachCallback( %NodeType[] det ){}
164             }
165 
166             %NodeType __%parent_node;
167             %NodeType[] __%childs_list;
168 
169         }
170         void %setParent( %NodeType p ) { __%parent_node = p; }
171 
172         public @property
173         {
174             %NodeType %parent() { return __%parent_node; }
175             const(%NodeType) %parent() const { return __%parent_node; }
176             %NodeType %parent( %NodeType p )
177             {
178                 if( __%parent_node !is null )
179                     __%parent_node.%detachChilds( this );
180                 __%parent_node = p;
181                 if( __%parent_node !is null )
182                     __%parent_node.%attachChilds( this );
183                 return p;
184             }
185 
186             %NodeType[] %childs() { return __%childs_list; }
187             const(%NodeType[]) %childs() const { return __%childs_list; }
188 
189             %NodeType[] %childs( %NodeType[] nch )
190             {
191                 __%childs_list = nch;
192                 return nch;
193             }
194         }
195 
196         final
197         {
198             public void %attachChilds( %NodeType[] att... )
199             in { assert( att.length > 0 ); } body
200             {
201                 import std.exception;
202                 import des.util.arch.tree;
203                 debug enum DEBUG = true; else enum DEBUG = false;
204 
205                 static if( __%release_nullchild_check || DEBUG )
206                     foreach( el; att )
207                         enforce( el !is null, new TNodeNullChildException );
208 
209                 static if( __%release_cycle_check || DEBUG )
210                     enforce( %cycleCheck( att ), new TNodeCycleException );
211 
212                 __%childs_list ~= att;
213                 foreach( el; att ) el.%setParent( this );
214                 %attachCallback( att );
215             }
216 
217             public void %detachChilds( %NodeType[] det... )
218             {
219                 %NodeType[] buf;
220                 foreach( ch; %childs )
221                     foreach( d; det )
222                         if( ch != d )
223                             buf ~= ch;
224                 foreach( d; det )
225                     d.%setParent( null );
226                 %childs = buf;
227                 %detachCallback( det );
228             }
229 
230             protected bool %cycleCheck( const(%NodeType)[] list... ) const
231             {
232                 const(%NodeType)[] plist = [this];
233 
234                 while( plist[$-1].%parent )
235                     plist ~= plist[$-1].%parent;
236 
237                 foreach( p; plist )
238                     foreach( elem; list )
239                         if( elem.%findInChilds(p) )
240                             return false;
241 
242                 return true;
243             }
244         }
245     }
246 }));
247 }
248 }
249 
250 ///
251 unittest
252 {
253     static class Test : TNode!(Test,"pre_","Suff")
254     {
255         mixin pre_TNodeHelperSuff!(true,true,true);
256 
257         string name;
258         this( string name ) { this.name = name; }
259 
260         void append( string kk )
261         {
262             name ~= kk;
263             foreach( ch; pre_childsSuff )
264                 ch.append( kk );
265         }
266     }
267 
268     auto e0 = new Test("a");
269     auto e1 = new Test("b");
270     auto e2 = new Test("c");
271     auto e3 = new Test("d");
272 
273     e1.pre_parentSuff = e0;
274     e2.pre_parentSuff = e1;
275 
276     e0.append( "ok" );
277 
278     e1.pre_attachChildsSuff( e3 );
279 
280     assert( e0.pre_childsSuff == [ e1 ] );
281     assert( e0.name == "aok" );
282     assert( e1.name == "bok" );
283     assert( e3.name == "d" );
284     assert( e3.pre_parentSuff == e1 );
285 }
286 
287 ///
288 unittest
289 {
290     static class Test : TNode!(Test,"a_"), TNode!(Test,"b_")
291     {
292         mixin a_TNodeHelper!(true,true,true);
293         mixin b_TNodeHelper!(true,true,true);
294 
295         string name;
296         this( string name ) { this.name = name; }
297 
298         void append( string kk )
299         {
300             name ~= kk;
301             foreach( ch; b_childs )
302                 ch.append( kk );
303         }
304     }
305 
306     auto e0 = new Test("e0");
307     auto e1 = new Test("e1");
308     auto e2 = new Test("e2");
309     auto e3 = new Test("e3");
310 
311     e2.a_parent = e0;
312     e2.b_parent = e1;
313 
314     e0.append( "ok" );
315 
316     assert( e0.name == "e0ok" );
317     assert( e2.name == "e2" );
318 }
319 
320 ///
321 unittest
322 {
323     static interface Node : TNode!Node
324     {
325         mixin template NodeHelper() { mixin TNodeHelper!true; }
326         void inc_self();
327 
328         final void inc()
329         {
330             inc_self();
331             foreach( ch; childs )
332                 ch.inc();
333         }
334     }
335 
336     static class Test : Node
337     {
338         mixin NodeHelper;
339 
340         int i;
341         this( int i ) { this.i = i; }
342         void inc_self() { i++; }
343     }
344 
345     auto a0 = new Test(10);
346     auto a1 = new Test(15);
347     a0.attachChilds( a1 );
348 
349     a0.inc();
350 
351     assert( a0.i == 11 );
352     assert( a1.i == 16 );
353 }
354 
355 ///
356 unittest
357 {
358     static class Test : TNode!Test { mixin TNodeHelper!(true, true, true); }
359 
360     auto a0 = new Test;
361     auto a1 = new Test;
362     auto a2 = new Test;
363 
364     assert( a0.parent is null );
365     assert( a1.parent is null );
366     assert( a2.parent is null );
367 
368     a0.attachChilds( a1 );
369     a1.attachChilds( a2 );
370 
371     assert( a0.childs.length == 1 );
372     assert( a1.childs.length == 1 );
373 
374     assert( mustExcept!TNodeCycleException({ a2.attachChilds(a0); }) );
375     assert( mustExcept!TNodeNullChildException({ a2.attachChilds(null); }) );
376 }
377 
378 unittest
379 {
380     static class Test : TNode!(Test,"a") { mixin aTNodeHelper!(true, true, true); }
381     auto a0 = new Test;
382     assert( a0.aParent is null );
383 }