File Coverage

blib/lib/Hash/Filler.pm
Criterion Covered Total %
statement 81 136 59.5
branch 27 62 43.5
condition 5 11 45.4
subroutine 14 20 70.0
pod 8 9 88.8
total 135 238 56.7


line stmt bran cond sub pod time code
1             package Hash::Filler;
2            
3 1     1   2040 use strict;
  1         2  
  1         64  
4 1     1   8 use Carp;
  1         3  
  1         131  
5 1     1   6 use vars qw($VERSION $DEBUG $indent);
  1         14  
  1         92  
6 1     1   8955 use Time::HiRes qw(gettimeofday tv_interval);
  1         7366  
  1         6  
7            
8             # How to check for the existence of an element
9            
10 1     1   590 use constant TRUE => 0; # Test if the value is true
  1         3  
  1         101  
11 1     1   7 use constant DEFINED => 1; # Use defined()
  1         2  
  1         49  
12 1     1   6 use constant EXISTS => 2; # Use exists() (default)
  1         4  
  1         47  
13            
14 1     1   5 use constant INDENT => 2; # How much to indent printouts
  1         2  
  1         2694  
15            
16             $VERSION = '1.40';
17             $DEBUG = '0';
18            
19             my $indent = 0;
20            
21             # Preloaded methods go here.
22            
23             sub new {
24 5     5 0 163 my $type = shift;
25 5   50     27 my $class = ref($type) || $type || "Hash::Filler";
26            
27 5         32 my $self = {
28             'rules' => {}, # All the rules we know about
29             'wild' => [], # Wildcard rules
30             'times' => [], # Accumulated times for each rule
31             'calls' => [], # How many times each rule has been used
32             'id' => 0, # Current rule id
33             'loop' => 1, # Avoid loops by default
34             'method' => EXISTS, # Which method to use to check for
35             # existence of a hash key
36             };
37            
38 5         21 bless $self, $class;
39             }
40            
41             sub _sort { # This is to be used by the sort
42             # built-in
43             return
44 0         0 $b->{'pref'} <=> $a->{'pref'} or
45 11 0 0 11   37 @{$a->{'prereq'}} <=> @{$b->{'prereq'}} or
  0         0  
46             $a->{'used'} <=> $b->{'used'};
47             }
48            
49             sub _print_rule {
50 0     0   0 my $self = shift;
51 0         0 my $rule = shift;
52 0         0 my $key = shift;
53            
54 0 0       0 printf("%s[%d] rule for key %s, used %s, pref %s, %s be used\n",
    0          
55             ' ' x $indent,
56             $rule->{'id'},
57             defined $rule->{'key'} ? $rule->{'key'} : '',
58             $rule->{'used'},
59             $rule->{'pref'},
60             $rule->{'use'} ? 'can' : 'cannot');
61 0         0 printf("%s|[called %d times (%0.6f secs)]\n",
62             ' ' x $indent,
63             $self->{'calls'}->[$rule->{'id'}],
64             $self->{'times'}->[$rule->{'id'}]);
65 0 0       0 if (defined $key) {
66 0         0 printf("%s|[called to get key %s]\n",
67             ' ' x $indent,
68             $key);
69             }
70 0         0 my $pre = 0;
71 0         0 foreach my $pr (sort @{$rule->{'prereq'}}) {
  0         0  
72 0         0 printf("%s+- prereq %s\n", ' ' x $indent, $pr);
73 0         0 ++$pre;
74             }
75 0 0       0 printf("%s+- No prereq\n", ' ' x $indent) unless $pre;
76             }
77            
78             sub dump_r_tree {
79 0     0 1 0 my $self = shift;
80 0         0 foreach my $key (keys %{$self->{'rules'}}) {
  0         0  
81 0         0 my $dumped = 0;
82 0         0 print "Rules for key $key:\n";
83 0         0 foreach my $rule (sort(_sort @{$self->{'rules'}->{$key}})) {
  0         0  
84 0         0 ++$dumped;
85 0         0 $self->_print_rule($rule);
86             }
87 0 0       0 print " No rules.\n" unless $dumped;
88             }
89 0         0 my $dumped = 0;
90 0         0 print "Wildcard rules:\n";
91 0         0 foreach my $rule (sort(_sort @{$self->{'wild'}})) {
  0         0  
92 0         0 ++$dumped;
93 0         0 $self->_print_rule($rule);
94             }
95 0 0       0 print " No rules.\n" unless $dumped;
96             }
97            
98             sub loop {
99 0     0 1 0 $_[0]->{'loop'} = $_[1];
100             }
101            
102             sub method {
103 0     0 1 0 $_[0]->{'method'} = $_[1];
104             }
105            
106             sub stats {
107 1     1 1 5 @{$_[0]->{'calls'}};
  1         5  
108             }
109            
110             sub profile {
111 0     0 1 0 @{$_[0]->{'times'}};
  0         0  
112             }
113            
114             sub remove {
115 0     0 1 0 my $self = shift;
116 0         0 my $id = shift;
117            
118 0 0       0 return unless $id;
119            
120 0         0 foreach my $key (keys %{$self->{'rules'}}) {
  0         0  
121 0         0 foreach my $rule (@{$self->{'rules'}->{$key}}) {
  0         0  
122 0 0       0 if ($rule->{'id'} == $id) {
123 0         0 $rule->{'use'} = 0;
124 0         0 return;
125             }
126             }
127             }
128 0         0 foreach my $rule (@{$self->{'wild'}}) {
  0         0  
129 0 0       0 if ($rule->{'id'} == $id) {
130 0         0 $rule->{'use'} = 0;
131 0         0 return;
132             }
133             }
134             }
135            
136             sub add {
137 15     15 1 106 my $ret;
138            
139 15 100       30 if (defined $_[1]) { # Specific rule
140 13 100       13 push @{$_[0]->{'rules'}->{$_[1]}}, {
  13         109  
141             'key' => $_[1],
142             'code' => $_[2],
143             'prereq' => $_[3],
144             'pref' => $_[4] ? $_[4] : 100,
145             'used' => 0,
146             'use' => 1,
147             'id' => $ret = ++ $_[0]->{'id'},
148             };
149             }
150             else { # Wildcard rule
151 2 100       3 push @{$_[0]->{'wild'}}, {
  2         14  
152             'key' => undef,
153             'code' => $_[2],
154             'prereq' => $_[3],
155             'pref' => $_[4] ? $_[4] : 100,
156             'used' => 0,
157             'use' => 1,
158             'id' => $ret = ++ $_[0]->{'id'},
159             };
160             }
161 15         29 $ret;
162             }
163            
164             sub _exists {
165 47     47   64 my $self = shift;
166 47         47 my $href = shift;
167 47         56 my $key = shift;
168            
169 47 50       121 if ($self->{'method'} == DEFINED) {
    50          
    0          
170 0 0       0 return 1 if defined $href->{$key};
171             }
172             elsif ($self->{'method'} == EXISTS) {
173 47 100       158 return 1 if exists $href->{$key};
174             }
175             elsif (ref $self->{'method'} eq 'CODE') {
176 0 0       0 return 1 if $self->{'method'}->($href, $key);
177             }
178             else {
179 0 0       0 return 1 if $href->{$key};
180             }
181 25         59 return 0;
182             }
183            
184             sub fill {
185 27     27 1 111 my $self = shift;
186 27         30 my $href = shift;
187 27         31 my $key = shift;
188 27         27 my $ret = 0;
189            
190 27 50       94 croak "->fill() must be given a hash reference"
191             unless ref($href) eq 'HASH';
192            
193             # Provide a quick exit if the hash
194             # key is already defined or if
195             # we have no rules to generate it.
196            
197 27         42 ++ $self->{'calls'}->[0]; # Keep the number of times ->fill
198             # has been called.
199            
200 27 100       52 return 1
201             if $self->_exists($href, $key);
202            
203 5         18 return 0
204             unless $self->{'rules'}->{$key} or
205 21 50 66     64 @{$self->{'wild'}};
206            
207             # Look through the available rules
208             # and try to find an execution plan
209             # to fill the requested $key.
210            
211 21         24 my @rulelist;
212            
213 21 100       46 if ($self->{'rules'}->{$key}) {
214 16         17 push @rulelist, sort(_sort @{$self->{'rules'}->{$key}});
  16         48  
215             }
216            
217 21         27 push @rulelist, sort(_sort @{$self->{'wild'}});
  21         51  
218            
219             RULE:
220 21         32 foreach my $rule (@rulelist) {
221            
222             next RULE # Watch out for infinite loops
223 23 100 66     106 if $self->{'loop'} and
224             $rule->{'used'};
225            
226 20         25 $rule->{'used'} ++; # Mark this rule as being used
227             # to control infinite recursion
228            
229 20         30 ++ $self->{'calls'}->[$rule->{'id'}];
230            
231             # Insure that all prerequisites
232             # are there before attempting to
233             # call this method
234            
235 20         24 foreach my $pr (@{$rule->{'prereq'}}) {
  20         37  
236            
237             # A rule cannot be invoked to resolve
238             # its own prerequisite as this might make
239             # no sense.
240            
241 20 100       40 if ($pr eq $key) {
242 1 50       4 if (defined $rule->{'key'}) {
243 0         0 croak "Rule "
244             . $rule->{'id'}
245             . " has itself as prerequisite";
246             }
247             else { # A wildcard rule...
248             next RULE
249 1 50       2 unless $self->_exists($href, $pr);
250             }
251             }
252            
253             # Recursive call. If required, attempt
254             # to fill this prerequisite using the
255             # available rules. If the prereq is
256             # already in the hash, this will return
257             # immediatly. The retval of this ->fill()
258             # is ignored as there might be more than
259             # one rule that can provide the missing
260             # prereq.
261            
262             # XXX - Note that we might want to return false from this rule if the fill
263             # method for a prereq returns false. The current implementation allows the
264             # method's return value control the behavior of ->fill more fine-granedly.
265            
266 19         26 $indent += INDENT;
267 19         76 $self->fill($href, $pr);
268 19         23 $indent -= INDENT;
269            
270             # Insure that the required hash
271             # buckets are already filled
272             # before attempting to call the
273             # user supplied function.
274            
275             next RULE
276 19 100       38 unless $self->_exists($href, $pr);
277             }
278            
279 16 50       35 $self->_print_rule($rule, $key) if $DEBUG;
280            
281             # Run and profile the execution of
282             # the user supplied method.
283            
284 16         66 my $time = [gettimeofday];
285 16         51 $ret = $rule->{'code'}->($href, $key);
286 16         107 $time = tv_interval($time);
287            
288 16         233 $self->{'times'}->[$rule->{'id'}] += $time;
289 16         28 $self->{'times'}->[0] += $time;
290             }
291             continue {
292 23         29 $rule->{'used'} --; # Rule is no longer used
293 23 100       69 return $ret # If a user-supplied sub was
294             if $ret; # succesful, we're done
295             }
296            
297 5         13 return 0; # No rule matched or was succesful.
298             }
299            
300             # Autoload methods go after =cut, and are processed by the autosplit program.
301            
302             1;
303             __END__