File Coverage

lib/Graph/Easy/Layout/Chain.pm
Criterion Covered Total %
statement 176 190 92.6
branch 66 98 67.3
condition 28 47 59.5
subroutine 13 14 92.8
pod 8 8 100.0
total 291 357 81.5


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 48     48   14205 use Graph::Easy::Base;
  48         55  
  48         1831  
10             $VERSION = '0.76';
11             @ISA = qw/Graph::Easy::Base/;
12              
13 48     48   159 use strict;
  48         58  
  48         986  
14 48     48   145 use warnings;
  48         57  
  48         1200  
15              
16 48     48   335 use Graph::Easy::Util qw(ord_values);
  48         54  
  48         2240  
17              
18             use constant {
19 48         66975 _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 48     48   173 };
  48         49  
24              
25             #############################################################################
26              
27             sub _init
28             {
29             # Generic init routine, to be overriden in subclasses.
30 794     794   802 my ($self,$args) = @_;
31              
32 794         2100 foreach my $k (sort keys %$args)
33             {
34 1588 50       4044 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         2099 $self->{$k} = $args->{$k};
40             }
41              
42 794         1061 $self->{end} = $self->{start};
43              
44             # store chain at node (to lookup node => chain info)
45 794         914 $self->{start}->{_chain} = $self;
46 794         1496 $self->{start}->{_next} = undef;
47              
48 794         792 $self->{len} = 1;
49              
50 794         1664 $self;
51             }
52              
53             sub start
54             {
55             # return first node in the chain
56 6     6 1 8 my $self = shift;
57              
58 6         19 $self->{start};
59             }
60              
61             sub end
62             {
63             # return last node in the chain
64 6     6 1 7 my $self = shift;
65              
66 6         17 $self->{end};
67             }
68              
69             sub add_node
70             {
71             # add a node at the end of the chain
72 357     357 1 382 my ($self, $node) = @_;
73              
74             # store at end
75 357         436 $self->{end}->{_next} = $node;
76 357         327 $self->{end} = $node;
77              
78             # store chain at node (to lookup node => chain info)
79 357         567 $node->{_chain} = $self;
80 357         601 $node->{_next} = undef;
81              
82 357         318 $self->{len} ++;
83              
84 357         438 $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 16 my ($self, $node) = @_;
92              
93 11 100       37 return $self->{len} unless defined $node;
94              
95 3         3 my $len = 0;
96 3         6 while (defined $node)
97             {
98 4         12 $len++; $node = $node->{_next};
  4         7  
99             }
100              
101 3         8 $len;
102             }
103              
104             sub nodes
105             {
106             # return all the nodes in the chain as a list, in order.
107 3     3 1 728 my $self = shift;
108              
109 3         5 my @nodes = ();
110 3         3 my $n = $self->{start};
111 3         5 while (defined $n)
112             {
113 12         9 push @nodes, $n;
114 12         17 $n = $n->{_next};
115             }
116              
117 3         8 @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 678 my ($self, $edge) = @_;
126              
127             # prevent doing it twice
128 605 50       922 return [] if $self->{_done}; $self->{_done} = 1;
  605         641  
129              
130 605         587 my @TODO = ();
131              
132 605         497 my $g = $self->{graph};
133              
134             # first, layout all the nodes in the chain:
135              
136             # start with first node
137 605         487 my $pre = $self->{start}; my $n = $pre->{_next};
  605         600  
138 605 100       929 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     896 if ($edge && ($edge->{to} == $pre) && ($edge->attribute('flow') || $edge->has_ports()))
      33        
      66        
143             {
144 103         281 push @TODO, $g->_action( _ACTION_CHAIN, $pre, 0, $edge->{from}, $edge);
145             }
146             else
147             {
148 101         237 push @TODO, $g->_action( _ACTION_NODE, $pre, 0, $edge );
149             }
150             }
151              
152 605 50       986 print STDERR "# Stack after first:\n" if $g->{debug};
153 605 50       797 $g->_dump_stack(@TODO) if $g->{debug};
154              
155 605         932 while (defined $n)
156             {
157 585 100       876 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         1082 my @edges = $g->edge($pre,$n);
165              
166 516         1068 push @TODO, $g->_action( _ACTION_CHAIN, $n, 0, $pre, $edges[0] );
167             }
168 585         517 $pre = $n;
169 585         943 $n = $n->{_next};
170             }
171              
172 605 50       960 print STDERR "# Stack after chaining:\n" if $g->{debug};
173 605 50       877 $g->_dump_stack(@TODO) if $g->{debug};
174              
175             # link from each node to the next
176 605         530 $pre = $self->{start}; $n = $pre->{_next};
  605         588  
177 605         892 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         908 for my $e (ord_values ( $pre->{edges}))
182             {
183             # skip selfloops and backward links, these will be done later
184 1250 100       2072 next if $e->{to} != $n;
185              
186 602 100       896 next unless exists $e->{_todo};
187              
188             # skip links from/to groups
189             next if $e->{to}->isa('Graph::Easy::Group') ||
190 582 50 33     3236 $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         894 push @TODO, [ _ACTION_TRACE, $e ];
196 582         788 delete $e->{_todo};
197             }
198              
199 585         458 } continue { $pre = $n; $n = $n->{_next}; }
  585         1040  
200              
201 605 50       848 print STDERR "# Stack after chain-linking:\n" if $g->{debug};
202 605 50       855 $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         584 $n = $self->{start};
208 605         858 while (defined $n)
209             {
210 1190         837 my @edges;
211              
212             my @count;
213              
214 1190 50       1520 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         1959 for my $e (ord_values ( $n->{edges}))
219             {
220             # skip selfloops, these will be done later
221 1941 100       3352 next if $e->{to} == $n;
222              
223 889 100       1509 next if !ref($e->{to}->{_chain});
224 883 50       1359 next if !ref($e->{from}->{_chain});
225              
226 883 100       1670 next if $e->has_ports();
227              
228             # skip links from/to groups
229             next if $e->{to}->isa('Graph::Easy::Group') ||
230 815 50 33     5020 $e->{from}->isa('Graph::Easy::Group');
231              
232 815 50       1322 print STDERR "# inter-chain link from $n->{name} to $e->{to}->{name}\n" if $g->{debug};
233              
234             # leaving the chain?
235 815 100       1410 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       1165 next unless exists $e->{_todo};
239              
240             # calculate for this edge, how far it goes
241 72         93 my $count = 0;
242 72         69 my $curr = $n;
243 72   100     290 while (defined $curr && $curr != $e->{to})
244             {
245 185         162 $curr = $curr->{_next}; $count ++;
  185         493  
246             }
247 72 100       145 if (!defined $curr)
248             {
249             # edge goes backward
250              
251             # start at $to
252 15         23 $curr = $e->{to};
253 15         21 $count = 0;
254 15   66     77 while (defined $curr && $curr != $e->{from})
255             {
256 32         27 $curr = $curr->{_next}; $count ++;
  32         88  
257             }
258 15 50       29 $count = 100000 if !defined $curr; # should not happen
259             }
260 72         125 push @edges, [ $count, $e ];
261 72         216 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         1799 for my $e (sort { $a->[0] <=> $b->[0] } @edges)
  8         14  
268             {
269 72         129 push @TODO, [ _ACTION_TRACE, $e->[1] ];
270 72         104 delete $e->[1]->{_todo};
271             }
272              
273 1190         2087 $n = $n->{_next};
274             }
275              
276             # also do all selfloops on $n
277 605         563 $n = $self->{start};
278 605         889 while (defined $n)
279             {
280             # for my $e (sort { $a->{to}->{name} cmp $b->{to}->{name} } values %{$n->{edges}})
281 1190         1632 for my $e (ord_values $n->{edges})
282             {
283 1941 100       2723 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     1462 next if $e->{from} != $n || $e->{to} != $n; # no selfloop?
290              
291 31         61 push @TODO, [ _ACTION_TRACE, $e ];
292 31         40 delete $e->{_todo};
293             }
294 1190         1933 $n = $n->{_next};
295             }
296              
297 605 50       895 print STDERR "# Stack after self-loops:\n" if $g->{debug};
298 605 50       869 $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         498 $n = $self->{start};
304 605         865 while (defined $n)
305             {
306              
307             # all chains that start at this node
308 1190         931 for my $e (sort { $a->{to}->{name} cmp $b->{to}->{name} } values %{$n->{edges}})
  1176         1464  
  1190         2047  
309             {
310 1941         1542 my $to = $e->{to};
311              
312             # skip links to groups
313 1941 50       4185 next if $to->isa('Graph::Easy::Group');
314              
315             # print STDERR "# chain-tracking to: $to->{name} $to->{_chain}\n";
316              
317 1941 100 66     6173 next unless exists $to->{_chain} && ref($to->{_chain}) =~ /Chain/;
318 1935         1453 my $chain = $to->{_chain};
319 1935 100       2922 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       296 push @TODO, @{ $chain->layout($e) } unless $chain->{_done};
  165         353  
327              
328             # link the edges to $to
329 165 100       348 next unless exists $e->{_todo}; # was already done above?
330              
331             # next if $e->has_ports();
332              
333 148         232 push @TODO, [ _ACTION_TRACE, $e ];
334 148         213 delete $e->{_todo};
335             }
336 1190         1737 $n = $n->{_next};
337             }
338              
339 605         1687 \@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 212 my ($self, $other, $where) = @_;
366              
367 214         191 my $g = $self->{graph};
368              
369 214 50       315 print STDERR "# panik: ", join(" \n",caller()),"\n" if !defined $other;
370              
371             print STDERR
372             "# Merging chain $other->{id} (len $other->{len}) into $self->{id} (len $self->{len})\n"
373 214 50       364 if $g->{debug};
374              
375             print STDERR
376             "# Merging from $where->{name} onwards\n"
377 214 50 33     390 if $g->{debug} && ref($where);
378              
379             # cannot merge myself into myself (without allocating infinitely memory)
380 214 50       333 return if $self == $other;
381              
382             # start at start as default
383 214 50 66     1018 $where = undef unless ref($where) && exists $where->{_chain} && $where->{_chain} == $other;
      33        
384              
385 214 100       318 $where = $other->{start} unless defined $where;
386              
387             # make all nodes from chain #1 belong to it (to detect loops)
388 214         207 my $n = $self->{start};
389 214         346 while (defined $n)
390             {
391 232         213 $n->{_chain} = $self;
392 232         402 $n = $n->{_next};
393             }
394              
395 214 50       333 print STDERR "# changed nodes\n" if $g->{debug};
396 214 50       313 $self->dump() if $g->{debug};
397              
398             # terminate at $where
399 214         244 $self->{end}->{_next} = $where;
400 214         209 $self->{end} = $other->{end};
401              
402             # start at joiner
403 214         194 $n = $where;
404 214         327 while (ref($n))
405             {
406 496         383 $n->{_chain} = $self;
407 496         331 my $pre = $n;
408 496         369 $n = $n->{_next};
409              
410             # sleep(1);
411             # print "# at $n->{name} $n->{_chain}\n" if ref($n);
412 496 50 66     1942 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         196 $self->{len} = 0; $n = $self->{start};
  214         188  
424 214         340 while (defined $n)
425             {
426 728         487 $self->{len}++; $n = $n->{_next};
  728         937  
427             }
428              
429             # print "done merging, dumping result:\n";
430             # $self->dump(); sleep(10);
431              
432 214 100 66     716 if (defined $other->{start} && $where == $other->{start})
433             {
434             # we absorbed the other chain completely, so drop it
435 165         166 $other->{end} = undef;
436 165         137 $other->{start} = undef;
437 165         162 $other->{len} = 0;
438             # caller is responsible for cleaning it up
439             }
440              
441 214 50       341 print STDERR "# after merging\n" if $g->{debug};
442 214 50       298 $self->dump() if $g->{debug};
443              
444 214         309 $self;
445             }
446              
447             1;
448             __END__