File Coverage

blib/lib/YATT/Lite/Util/CycleDetector.pm
Criterion Covered Total %
statement 65 68 95.5
branch 10 16 62.5
condition 1 3 33.3
subroutine 16 16 100.0
pod 0 9 0.0
total 92 112 82.1


line stmt bran cond sub pod time code
1             # -*- coding: utf-8 -*-
2              
3             # This package is used to implement modified version of following algorithm:
4             #
5             # http://en.wikipedia.org/wiki/Topological_sorting#CITEREFCormenLeisersonRivestStein2001
6             #
7             # Cormen, Thomas H.; Leiserson, Charles E.; Rivest, Ronald L.;
8             # Stein, Clifford (2001),
9             # "Section 22.4: Topological sort", Introduction to Algorithms (2nd ed.),
10             # MIT Press and McGraw-Hill, pp. 549–552, ISBN 0-262-03293-7.
11             #
12              
13             package YATT::Lite::Util::CycleDetector;
14 13     13   81 use strict;
  13         26  
  13         381  
15 13     13   62 use warnings qw(FATAL all NONFATAL misc);
  13         27  
  13         424  
16 13     13   63 use Carp;
  13         28  
  13         733  
17              
18 13     13   93 use Exporter qw/import/;
  13         26  
  13         870  
19             our @EXPORT_OK = qw/Visits/;
20              
21             sub Visits () {__PACKAGE__}
22 13     13   81 use YATT::Lite::MFields qw/nodes time/;
  13         31  
  13         905  
23              
24             use YATT::Lite::Types
25 13     13   1511 ([Node => fields => [qw/fname discovered finished color parent/]]);
  13         36  
  13         118  
26             use YATT::Lite::Util::Enum
27 13         114 (NTYPE_ => [qw/WHITE GRAY BLACK/]
28 13     13   93 , EDGE_ => [qw/TREE BACK FORW CROSS/]);
  13         33  
29              
30             sub start {
31 54     54 0 159 my ($pack, $fname) = @_;
32 54         159 my Visits $vis = bless {}, $pack;
33 54         215 $vis->{time} = 0;
34 54         228 $vis->ensure_make_node($fname);
35 54         205 $vis->visit_node($fname);
36 54         171 $vis;
37             }
38              
39             sub fname2id {
40 303     303 0 576 (my Visits $vis, my $fname) = @_;
41 303         3414 my ($dev, $inode) = stat($fname);
42 303 50       763 if (grep {$_ eq ''} $dev, $inode) {
  606         1589  
43 0         0 $fname; # Workaround
44             } else {
45 303         1330 join "_", $dev, $inode;
46             }
47             }
48              
49             sub has_node {
50 9     9 0 23 (my Visits $vis, my $fname) = @_;
51 9         27 $vis->{nodes}{$vis->fname2id($fname)};
52             }
53              
54             sub ensure_make_node {
55 94     94 0 261 (my Visits $vis, my @path) = @_;
56 94         217 foreach my $fname (@path) {
57 94 100       304 next if $vis->{nodes}{$vis->fname2id($fname)};
58 85         268 $vis->make_node($fname);
59             }
60 94         504 @path;
61             }
62              
63             sub make_node {
64 85     85 0 198 (my Visits $vis, my ($fname)) = @_;
65 85         283 $vis->{nodes}{$vis->fname2id($fname)} = my Node $node = {};
66 85         231 $node->{fname} = $fname;
67 85         205 $node->{color} = NTYPE_WHITE;
68 85         213 $node;
69             }
70              
71             sub visit_node {
72 70     70 0 200 (my Visits $vis, my ($fname, $parent)) = @_;
73 70 50       213 my Node $node = $vis->{nodes}{$vis->fname2id($fname)}
74             or croak "No such path in visits! $fname";
75 70         224 $node->{color} = NTYPE_GRAY;
76 70         178 $node->{discovered} = ++$vis->{time};
77 70 50       231 $node->{parent} = $vis->{nodes}{$vis->fname2id($parent)} if $parent;
78 70         152 $node;
79             }
80              
81             sub finish_node {
82 28     28 0 68 (my Visits $vis, my $fname) = @_;
83 28 50       82 my Node $node = $vis->{nodes}{$vis->fname2id($fname)}
84             or croak "No such path in visits! $fname";
85 28         62 $node->{color} = NTYPE_BLACK;
86 28         61 $node->{finished} = ++$vis->{time};
87 28         106 $node;
88             }
89              
90             sub check_cycle {
91 17     17 0 43 (my Visits $vis, my ($to, $from)) = @_;
92 17 50       54 my Node $dest = $vis->{nodes}{$vis->fname2id($to)}
93             or croak "No such path in visits! $to";
94 17 100       49 if ($dest->{color} == NTYPE_WHITE) {
    50          
95             # tree edge
96 16         54 $vis->visit_node($to);
97             } elsif ($dest->{color} == NTYPE_GRAY) {
98             # back edge!
99 1         5 return [$to, $vis->list_cycle($dest)]
100             } else {
101             # forward or cross
102             }
103 16         53 return;
104             }
105              
106             sub list_cycle {
107 1     1 0 3 (my Visits $vis, my Node $node) = @_;
108 1         2 my @path;
109 1   33     8 while ($node and $node->{parent}) {
110 0         0 $node = $node->{parent};
111 0         0 push @path, $node->{fname};
112             }
113 1         5 @path;
114             }
115              
116             1;