File Coverage

lib/Graph/Easy/Layout/Chain.pm
Criterion Covered Total %
statement 176 190 92.6
branch 66 98 67.3
condition 29 47 61.7
subroutine 13 14 92.8
pod 8 8 100.0
total 292 357 81.7


line stmt bran cond sub pod time code
1             #############################################################################
2             # One chain of nodes in a Graph::Easy - used internally for layouts.
3             #
4             # (c) by Tels 2004-2006. Part of Graph::Easy
5             #############################################################################
6              
7             package Graph::Easy::Layout::Chain;
8              
9 49     49   36395 use Graph::Easy::Base;
  49         115  
  49         8728  
10             $VERSION = '0.75';
11             @ISA = qw/Graph::Easy::Base/;
12              
13 49     49   294 use strict;
  49         121  
  49         1910  
14 49     49   275 use warnings;
  49         101  
  49         2127  
15              
16 49     49   957 use Graph::Easy::Util qw(ord_values);
  49         129  
  49         5981  
17              
18             use constant {
19 49         126640 _ACTION_NODE => 0, # place node somewhere
20             _ACTION_TRACE => 1, # trace path from src to dest
21             _ACTION_CHAIN => 2, # place node in chain (with parent)
22             _ACTION_EDGES => 3, # trace all edges (shortes connect. first)
23 49     49   306 };
  49         213  
24              
25             #############################################################################
26              
27             sub _init
28             {
29             # Generic init routine, to be overriden in subclasses.
30 794     794   1945 my ($self,$args) = @_;
31            
32 794         3168 foreach my $k (sort keys %$args)
33             {
34 1588 50       28775 if ($k !~ /^(start|graph)\z/)
35             {
36 0         0 require Carp;
37 0         0 Carp::confess ("Invalid argument '$k' passed to __PACKAGE__->new()");
38             }
39 1588         4929 $self->{$k} = $args->{$k};
40             }
41            
42 794         3921 $self->{end} = $self->{start};
43            
44             # store chain at node (to lookup node => chain info)
45 794         1696 $self->{start}->{_chain} = $self;
46 794         3394 $self->{start}->{_next} = undef;
47              
48 794         1865 $self->{len} = 1;
49              
50 794         3614 $self;
51             }
52              
53             sub start
54             {
55             # return first node in the chain
56 6     6 1 13 my $self = shift;
57              
58 6         32 $self->{start};
59             }
60              
61             sub end
62             {
63             # return last node in the chain
64 6     6 1 13 my $self = shift;
65              
66 6         32 $self->{end};
67             }
68              
69             sub add_node
70             {
71             # add a node at the end of the chain
72 357     357 1 645 my ($self, $node) = @_;
73              
74             # store at end
75 357         952 $self->{end}->{_next} = $node;
76 357         649 $self->{end} = $node;
77              
78             # store chain at node (to lookup node => chain info)
79 357         2004 $node->{_chain} = $self;
80 357         1583 $node->{_next} = undef;
81            
82 357         722 $self->{len} ++;
83              
84 357         810 $self;
85             }
86              
87             sub length
88             {
89             # Return the length of the chain in nodes. Takes optional
90             # node from where to calculate length.
91 11     11 1 28 my ($self, $node) = @_;
92              
93 11 100       65 return $self->{len} unless defined $node;
94              
95 3         6 my $len = 0;
96 3         10 while (defined $node)
97             {
98 4         6 $len++; $node = $node->{_next};
  4         12  
99             }
100              
101 3         16 $len;
102             }
103              
104             sub nodes
105             {
106             # return all the nodes in the chain as a list, in order.
107 3     3 1 1325 my $self = shift;
108              
109 3         7 my @nodes = ();
110 3         7 my $n = $self->{start};
111 3         13 while (defined $n)
112             {
113 12         17 push @nodes, $n;
114 12         30 $n = $n->{_next};
115             }
116              
117 3         15 @nodes;
118             }
119              
120             sub layout
121             {
122             # Return an action stack containing the nec. actions to
123             # lay out the nodes in the chain, plus any connections between
124             # them.
125 605     605 1 1115 my ($self, $edge) = @_;
126              
127             # prevent doing it twice
128 605 50       1992 return [] if $self->{_done}; $self->{_done} = 1;
  605         1348  
129              
130 605         1241 my @TODO = ();
131              
132 605         1248 my $g = $self->{graph};
133              
134             # first, layout all the nodes in the chain:
135              
136             # start with first node
137 605         1191 my $pre = $self->{start}; my $n = $pre->{_next};
  605         1026  
138 605 100       1829 if (exists $pre->{_todo})
139             {
140             # edges with a flow attribute must be handled differently
141             # XXX TODO: the test for attribute('flow') might be wrong (raw_attribute()?)
142 204 100 100     1514 if ($edge && ($edge->{to} == $pre) && ($edge->attribute('flow') || $edge->has_ports()))
      33        
      66        
143             {
144 103         778 push @TODO, $g->_action( _ACTION_CHAIN, $pre, 0, $edge->{from}, $edge);
145             }
146             else
147             {
148 101         413 push @TODO, $g->_action( _ACTION_NODE, $pre, 0, $edge );
149             }
150             }
151              
152 605 50       2015 print STDERR "# Stack after first:\n" if $g->{debug};
153 605 50       1429 $g->_dump_stack(@TODO) if $g->{debug};
154              
155 605         2102 while (defined $n)
156             {
157 585 100       1707 if (exists $n->{_todo})
158             {
159             # CHAIN means if $n isn't placed yet, it will be done with
160             # $pre as parent:
161              
162             # in case there are multiple edges to the target node, use the first
163             # one to determine the flow:
164 516         2832 my @edges = $g->edge($pre,$n);
165              
166 516         2081 push @TODO, $g->_action( _ACTION_CHAIN, $n, 0, $pre, $edges[0] );
167             }
168 585         921 $pre = $n;
169 585         2377 $n = $n->{_next};
170             }
171              
172 605 50       1911 print STDERR "# Stack after chaining:\n" if $g->{debug};
173 605 50       1766 $g->_dump_stack(@TODO) if $g->{debug};
174              
175             # link from each node to the next
176 605         1101 $pre = $self->{start}; $n = $pre->{_next};
  605         1025  
177 605         1699 while (defined $n)
178             {
179             # first do edges going from P to N
180             #for my $e (sort { $a->{to}->{name} cmp $b->{to}->{name} } values %{$pre->{edges}})
181 585         1885 for my $e (ord_values ( $pre->{edges}))
182             {
183             # skip selfloops and backward links, these will be done later
184 1250 100       3710 next if $e->{to} != $n;
185              
186 602 100       2105 next unless exists $e->{_todo};
187              
188             # skip links from/to groups
189 582 50 33     7806 next if $e->{to}->isa('Graph::Easy::Group') ||
190             $e->{from}->isa('Graph::Easy::Group');
191              
192             # # skip edges with a flow
193             # next if exists $e->{att}->{start} || exist $e->{att}->{end};
194              
195 582         1781 push @TODO, [ _ACTION_TRACE, $e ];
196 582         1903 delete $e->{_todo};
197             }
198              
199 585         873 } continue { $pre = $n; $n = $n->{_next}; }
  585         2081  
200              
201 605 50       1754 print STDERR "# Stack after chain-linking:\n" if $g->{debug};
202 605 50       1483 $g->_dump_stack(@TODO) if $g->{debug};
203              
204             # Do all other links inside the chain (backwards, going forward more than
205             # one node etc)
206              
207 605         1001 $n = $self->{start};
208 605         1769 while (defined $n)
209             {
210 1190         1590 my @edges;
211              
212             my @count;
213              
214 1190 50       3333 print STDERR "# inter-chain link from $n->{name}\n" if $g->{debug};
215              
216             # gather all edges starting at $n, but do the ones with a flow first
217             # for my $e (sort { $a->{to}->{name} cmp $b->{to}->{name} } values %{$n->{edges}})
218 1190         4039 for my $e (ord_values ( $n->{edges}))
219             {
220             # skip selfloops, these will be done later
221 1941 100       6683 next if $e->{to} == $n;
222              
223 889 100       3019 next if !ref($e->{to}->{_chain});
224 883 50       2350 next if !ref($e->{from}->{_chain});
225              
226 883 100       9812 next if $e->has_ports();
227              
228             # skip links from/to groups
229 815 50 33     11957 next if $e->{to}->isa('Graph::Easy::Group') ||
230             $e->{from}->isa('Graph::Easy::Group');
231              
232 815 50       1984 print STDERR "# inter-chain link from $n->{name} to $e->{to}->{name}\n" if $g->{debug};
233              
234             # leaving the chain?
235 815 100       2956 next if $e->{to}->{_chain} != $self;
236              
237             # print STDERR "# trying for $n->{name}:\t $e->{from}->{name} to $e->{to}->{name}\n";
238 587 100       2400 next unless exists $e->{_todo};
239              
240             # calculate for this edge, how far it goes
241 72         126 my $count = 0;
242 72         112 my $curr = $n;
243 72   100     463 while (defined $curr && $curr != $e->{to})
244             {
245 185         411 $curr = $curr->{_next}; $count ++;
  185         836  
246             }
247 72 100       195 if (!defined $curr)
248             {
249             # edge goes backward
250              
251             # start at $to
252 15         288 $curr = $e->{to};
253 15         35 $count = 0;
254 15   66     103 while (defined $curr && $curr != $e->{from})
255             {
256 32         58 $curr = $curr->{_next}; $count ++;
  32         142  
257             }
258 15 50       48 $count = 100000 if !defined $curr; # should not happen
259             }
260 72         294 push @edges, [ $count, $e ];
261 72         430 push @count, [ $count, $e->{from}->{name}, $e->{to}->{name} ];
262             }
263              
264             # use Data::Dumper; print STDERR "count\n", Dumper(@count);
265              
266             # do edges, shortest first
267 1190         4538 for my $e (sort { $a->[0] <=> $b->[0] } @edges)
  8         25  
268             {
269 72         239 push @TODO, [ _ACTION_TRACE, $e->[1] ];
270 72         220 delete $e->[1]->{_todo};
271             }
272              
273 1190         4799 $n = $n->{_next};
274             }
275            
276             # also do all selfloops on $n
277 605         1071 $n = $self->{start};
278 605         1567 while (defined $n)
279             {
280             # for my $e (sort { $a->{to}->{name} cmp $b->{to}->{name} } values %{$n->{edges}})
281 1190         3915 for my $e (ord_values $n->{edges})
282             {
283 1941 100       5318 next unless exists $e->{_todo};
284              
285             # print STDERR "# $e->{from}->{name} to $e->{to}->{name} on $n->{name}\n";
286             # print STDERR "# ne $e->{to} $n $e->{id}\n"
287             # if $e->{from} != $n || $e->{to} != $n; # no selfloop?
288              
289 515 100 100     2658 next if $e->{from} != $n || $e->{to} != $n; # no selfloop?
290              
291 31         94 push @TODO, [ _ACTION_TRACE, $e ];
292 31         120 delete $e->{_todo};
293             }
294 1190         5196 $n = $n->{_next};
295             }
296              
297 605 50       1957 print STDERR "# Stack after self-loops:\n" if $g->{debug};
298 605 50       1413 $g->_dump_stack(@TODO) if $g->{debug};
299              
300             # XXX TODO
301             # now we should do any links that start or end at this chain, recursively
302              
303 605         1035 $n = $self->{start};
304 605         1610 while (defined $n)
305             {
306              
307             # all chains that start at this node
308 1190         1621 for my $e (sort { $a->{to}->{name} cmp $b->{to}->{name} } values %{$n->{edges}})
  1187         3171  
  1190         4282  
309             {
310 1941         3180 my $to = $e->{to};
311              
312             # skip links to groups
313 1941 50       11224 next if $to->isa('Graph::Easy::Group');
314              
315             # print STDERR "# chain-tracking to: $to->{name} $to->{_chain}\n";
316              
317 1941 100 66     16492 next unless exists $to->{_chain} && ref($to->{_chain}) =~ /Chain/;
318 1935         2753 my $chain = $to->{_chain};
319 1935 100       6585 next if $chain->{_done};
320              
321             # print STDERR "# chain-tracking to: $to->{name}\n";
322              
323             # pass the edge along, in case it has a flow
324             # my @pass = ();
325             # push @pass, $e if $chain->{_first} && $e->{to} == $chain->{_first};
326 165 50       446 push @TODO, @{ $chain->layout($e) } unless $chain->{_done};
  165         622  
327              
328             # link the edges to $to
329 165 100       599 next unless exists $e->{_todo}; # was already done above?
330              
331             # next if $e->has_ports();
332              
333 148         424 push @TODO, [ _ACTION_TRACE, $e ];
334 148         479 delete $e->{_todo};
335             }
336 1190         3953 $n = $n->{_next};
337             }
338            
339 605         3057 \@TODO;
340             }
341              
342             sub dump
343             {
344             # dump the chain to STDERR
345 0     0 1 0 my ($self, $indent) = @_;
346              
347 0 0       0 $indent = '' unless defined $indent;
348              
349 0         0 print STDERR "#$indent chain id $self->{id} (len $self->{len}):\n";
350 0 0 0     0 print STDERR "#$indent is empty\n" and return if $self->{len} == 0;
351              
352 0         0 my $n = $self->{start};
353 0         0 while (defined $n)
354             {
355 0         0 print STDERR "#$indent $n->{name} (chain id: $n->{_chain}->{id})\n";
356 0         0 $n = $n->{_next};
357             }
358 0         0 $self;
359             }
360              
361             sub merge
362             {
363             # take another chain, and merge it into ourselves. If $where is defined,
364             # absorb only the nodes from $where onwards (instead of all of them).
365 214     214 1 376 my ($self, $other, $where) = @_;
366              
367 214         549 my $g = $self->{graph};
368              
369 214 50       595 print STDERR "# panik: ", join(" \n",caller()),"\n" if !defined $other;
370              
371 214 50       585 print STDERR
372             "# Merging chain $other->{id} (len $other->{len}) into $self->{id} (len $self->{len})\n"
373             if $g->{debug};
374              
375 214 50 33     656 print STDERR
376             "# Merging from $where->{name} onwards\n"
377             if $g->{debug} && ref($where);
378            
379             # cannot merge myself into myself (without allocating infinitely memory)
380 214 50       606 return if $self == $other;
381              
382             # start at start as default
383 214 50 66     2119 $where = undef unless ref($where) && exists $where->{_chain} && $where->{_chain} == $other;
      66        
384              
385 214 100       673 $where = $other->{start} unless defined $where;
386            
387             # make all nodes from chain #1 belong to it (to detect loops)
388 214         347 my $n = $self->{start};
389 214         510 while (defined $n)
390             {
391 232         352 $n->{_chain} = $self;
392 232         580 $n = $n->{_next};
393             }
394              
395 214 50       702 print STDERR "# changed nodes\n" if $g->{debug};
396 214 50       486 $self->dump() if $g->{debug};
397              
398             # terminate at $where
399 214         643 $self->{end}->{_next} = $where;
400 214         383 $self->{end} = $other->{end};
401              
402             # start at joiner
403 214         242 $n = $where;
404 214         666 while (ref($n))
405             {
406 496         695 $n->{_chain} = $self;
407 496         514 my $pre = $n;
408 496         657 $n = $n->{_next};
409              
410             # sleep(1);
411             # print "# at $n->{name} $n->{_chain}\n" if ref($n);
412 496 50 66     3163 if (ref($n) && defined $n->{_chain} && $n->{_chain} == $self) # already points into ourself?
      66        
413             {
414             # sleep(1);
415             # print "# pre $pre->{name} $pre->{_chain}\n";
416 0         0 $pre->{_next} = undef; # terminate
417 0         0 $self->{end} = $pre;
418 0         0 last;
419             }
420             }
421              
422             # could speed this up
423 214         334 $self->{len} = 0; $n = $self->{start};
  214         305  
424 214         469 while (defined $n)
425             {
426 728         860 $self->{len}++; $n = $n->{_next};
  728         1475  
427             }
428              
429             # print "done merging, dumping result:\n";
430             # $self->dump(); sleep(10);
431              
432 214 100 66     1148 if (defined $other->{start} && $where == $other->{start})
433             {
434             # we absorbed the other chain completely, so drop it
435 165         353 $other->{end} = undef;
436 165         231 $other->{start} = undef;
437 165         260 $other->{len} = 0;
438             # caller is responsible for cleaning it up
439             }
440              
441 214 50       522 print STDERR "# after merging\n" if $g->{debug};
442 214 50       482 $self->dump() if $g->{debug};
443              
444 214         644 $self;
445             }
446              
447             1;
448             __END__