File Coverage

blib/lib/Class/DbC.pm
Criterion Covered Total %
statement 169 175 96.5
branch 52 60 86.6
condition 12 17 70.5
subroutine 34 34 100.0
pod 0 1 0.0
total 267 287 93.0


line stmt bran cond sub pod time code
1             package Class::DbC;
2              
3             our $VERSION = '0.000002';
4             $VERSION = eval $VERSION;
5              
6 14     14   1855640 use strict;
  14         174  
  14         501  
7 14     14   7497 use Class::Method::Modifiers qw(install_modifier);
  14         24270  
  14         904  
8 14     14   107 use Carp;
  14         30  
  14         779  
9 14     14   7724 use Module::Runtime qw(require_module);
  14         28013  
  14         97  
10 14     14   9188 use Params::Validate qw(:all);
  14         106200  
  14         3009  
11 14     14   9459 use Storable qw( dclone );
  14         49682  
  14         6008  
12              
13             my %Spec_for;
14             my %Contract_pkg_for;
15              
16             my %Contract_validation_spec = (
17             type => HASHREF,
18             optional => 1,
19             );
20              
21             sub import {
22 15     15   2184 strict->import();
23 15         43 my $class = shift;
24 15         961 my %arg = validate(@_, {
25             interface => \%Contract_validation_spec,
26             invariant => \%Contract_validation_spec,
27             extends => { type => SCALAR, optional => 1 },
28             clone_with => { type => CODEREF, optional => 1 },
29             constructor_name => { type => SCALAR, default => 'new' },
30             });
31              
32 15         185 my $caller_pkg = (caller)[0];
33 15         391 $Spec_for{ $caller_pkg } = \%arg;
34 15         101 _handle_extentions($caller_pkg, $arg{extends});
35 15         67 _add_governor($caller_pkg);
36             }
37              
38             sub merge {
39 1     1 0 6 my ($h1, $h2) = @_;
40              
41 1         3 foreach my $k (keys %{ $h2 }) {
  1         7  
42 2 100       11 if (exists $h1->{$k}) {
43 1 50 33     12 if ( ref $h1->{$k} eq 'HASH'
44             && ref $h2->{$k} eq 'HASH'
45             ) {
46 0         0 merge($h1->{$k}, $h2->{$k});
47             }
48             }
49             else {
50 1         5 $h1->{$k} = $h2->{$k};
51             }
52             }
53             }
54              
55             sub _handle_extentions {
56 15     15   70 my ($pkg, $super) = @_;
57              
58 15 100       82 return unless $super;
59              
60 1         7 require_module($super);
61 1         17 merge($Spec_for{$pkg}, $Spec_for{$super});
62             }
63              
64             sub _add_governor {
65 15     15   54 my ($pkg) = @_;
66              
67 14     14   138 no strict 'refs';
  14         43  
  14         22685  
68 15         40 *{"${pkg}::govern"} = \&_govern;
  15         698  
69             }
70              
71             sub _govern {
72 18     18   29415 my $class = shift;
73 18         582 my ($pkg, $opt) = validate_pos(@_,
74             { type => SCALAR },
75             { type => HASHREF, default => { all => 1 } },
76             );
77 18         163 _validate_govern_options(%$opt);
78            
79 18 100 100     281 if ($opt->{all}
      100        
80             || ($opt->{emulate} && scalar keys %$opt == 1 )) {
81 12         79 $opt->{$_} = 1 for qw/pre post invariant/;
82             }
83              
84 18         70 my $interface_hash = $Spec_for{$class}{interface};
85 18 50       87 scalar keys %$interface_hash > 0
86             or confess "Contract $class has no specified methods";
87              
88 18         46 my $invariant_hash = $Spec_for{$class}{invariant};
89              
90 18         70 my $contract_pkg_prefix = _contract_pkg_prefix($class, $pkg);
91              
92 18         45 my $target_pkg = $pkg;
93 18         39 my $emulated = $pkg;
94              
95 18 100       67 if ($opt->{emulate}) {
96 11         31 my @types = grep { $opt->{$_} } qw[invariant post pre];
  33         97  
97 11         39 my $key = join '_', @types;
98              
99 11         61 $Contract_pkg_for{$class}{$pkg}{$key} = "${contract_pkg_prefix}$key";
100 11         48 ($emulated, $target_pkg) = _emulate($class, $pkg, $key);
101             }
102 18         57 foreach my $name (keys %{ $interface_hash }) {
  18         92  
103 126 50       27972 $pkg->can($name)
104             or confess "Class $pkg does not have a '$name' method, which is required by $class";
105              
106 126 100       420 if ($opt->{pre}) {
107 98         219 my $contract = $interface_hash->{$name};
108 98         405 _validate_contract_definition(%$contract);
109 98         609 _add_pre_conditions($class, $target_pkg, $name, $contract->{precond});
110             }
111 126 100       5763 if ($opt->{post}) {
112 84         168 my $contract = $interface_hash->{$name};
113 84         277 _validate_contract_definition(%$contract);
114 84         442 _add_post_conditions($class, $target_pkg, $name, $contract->{postcond});
115             }
116 126 100 66     12310 if ($opt->{invariant} && %$invariant_hash) {
117 105         279 _add_invariants($class, $target_pkg, $name, $invariant_hash, $emulated);
118             }
119             }
120 18 100       3687 if ($opt->{emulate}) {
121 11         59 return $emulated;
122             }
123             }
124              
125             sub _validate_contract_definition {
126 182     182   2507 validate(@_, {
127             precond => \%Contract_validation_spec,
128             postcond => \%Contract_validation_spec,
129             });
130             }
131              
132             sub _contract_pkg_prefix {
133 29     29   83 my ($class, $pkg) = @_;
134              
135 29         163 sprintf '%s_%s_', $pkg, $class;
136             }
137              
138             sub _add_pre_conditions {
139 98     98   410 my ($class, $pkg, $name, $pre_cond_hash) = @_;
140              
141 98 100       320 return unless $pre_cond_hash;
142              
143             my $guard = sub {
144 18     18   746 foreach my $desc (keys %{ $pre_cond_hash }) {
  18         89  
145 18         56 my $sub = $pre_cond_hash->{$desc};
146 18 50       88 ref $sub eq 'CODE'
147             or confess "precondition of $class, '$desc' on '$name' is not a code ref";
148 18 100       99 $sub->(@_)
149             or confess "Precondition '$desc' on '$name', mandated by $class is not satisfied";
150             }
151 28         146 };
152 28         117 install_modifier($pkg, 'before', $name, $guard);
153             }
154              
155             sub _add_post_conditions {
156 84     84   269 my ($class, $pkg, $name, $post_cond_hash) = @_;
157            
158 84 100       237 return unless $post_cond_hash;
159              
160 36   50     215 my $cloner = $Spec_for{$class}{clone_with} || \&dclone;
161              
162             my $guard = sub {
163 44     44   3176 my $orig = shift;
164 44         89 my $self = shift;
165              
166 44         82 my @old;
167 44         105 my @invocant = ($self);
168              
169 44 100       225 my $type = ref $self ? 'object' : 'class';
170 44 100       151 if ($type eq 'object') {
171 34         2611 @old = ( $cloner->($self) );
172             }
173 44         245 my $results = [$orig->($self, @_)];
174 44         667 my $results_to_check = $results;
175              
176 44 100 66     197 if ($type eq 'class' && $name eq $Spec_for{$class}{constructor_name}) {
177 10         28 $results_to_check = $results->[0];
178 10         33 @invocant = ();
179             }
180              
181 44         98 foreach my $desc (keys %{ $post_cond_hash }) {
  44         179  
182 73         1265 my $sub = $post_cond_hash->{$desc};
183 73 50       232 ref $sub eq 'CODE'
184             or confess "postcondition of $class, '$desc' on '$name' is not a code ref";
185              
186 73 100       288 $sub->(@invocant, @old, $results_to_check, @_)
187             or confess "Method '$pkg::$name' failed postcondition '$desc' mandated by $class";
188             }
189 38 100       2132 return unless defined wantarray;
190 9 100       115 return wantarray ? @$results : $results->[0];
191 36         214 };
192 36         162 install_modifier($pkg, 'around', $name, $guard);
193             }
194              
195             sub _add_invariants {
196 105     105   335 my ($class, $pkg, $name, $invariant_hash, $emulated) = @_;
197            
198             my $guard = sub {
199             # skip methods called by the invariant
200 798 100   798   47109 return if (caller 1)[0] eq $class;
201 384 100       12916 return if (caller 2)[0] eq $class;
202              
203 112         2707 my $self = shift;
204 112 50       299 return unless ref $self;
205              
206 112         194 foreach my $desc (keys %{ $invariant_hash }) {
  112         342  
207 112         225 my $sub = $invariant_hash->{$desc};
208 112 50       344 ref $sub eq 'CODE'
209             or confess "invariant of $class, '$desc' is not a code ref";
210 112 100       309 $sub->($self)
211             or confess "Invariant '$desc' mandated by $class has been violated";
212             }
213 105         549 };
214              
215 105 100       418 if ( $name eq $Spec_for{$class}{constructor_name} ) {
216             my $around = sub {
217 13     13   1059 my $orig = shift;
218 13         42 my $class = shift;
219 13         278 my $obj = $orig->($class, @_);
220 10         136 $guard->($obj);
221 7         367 return $obj;
222 15         82 };
223 15         71 install_modifier($pkg, 'around', $name, $around);
224             }
225             else {
226 90         253 foreach my $type ( qw[before after] ) {
227 180         16108 install_modifier($pkg, $type, $name, $guard);
228             }
229             }
230             }
231              
232             sub _emulate {
233 11     11   36 my ($class, $pkg, $key) = @_;
234              
235 11         34 my $contract_pkg = $Contract_pkg_for{$class}{$pkg}{$key};
236 11         48 _add_super($pkg, $contract_pkg);
237              
238 11         39 my $emulated = sprintf '%semulated', _contract_pkg_prefix($class, $pkg);
239 11         50 _setup_forwards($class, $pkg, $emulated, $contract_pkg);
240              
241 11         82 return ($emulated, $contract_pkg);
242             }
243              
244             sub _add_super {
245 11     11   33 my ($super, $pkg) = @_;
246              
247 14     14   153 no strict 'refs';
  14         54  
  14         2546  
248              
249 11 50       22 if ( @{"${pkg}::ISA"} ) {
  11         148  
250 0         0 my $between = shift @{"${pkg}::ISA"};
  0         0  
251 0         0 unshift @{"${pkg}::ISA"}, $super;
  0         0  
252 0         0 _add_super($between, $super);
253             }
254             else {
255 11         27 unshift @{"${pkg}::ISA"}, $super;
  11         129  
256             }
257             }
258              
259             sub _setup_forwards {
260 11     11   36 my ($class, $orig_pkg, $from_pkg, $to_pkg) = @_;
261              
262 11         22 my $version;
263             {
264 14     14   128 no strict 'refs';
  14         52  
  14         4928  
  11         18  
265 11         18 ${"${from_pkg}::Target"} = $to_pkg;
  11         72  
266 11         22 $version = ${"${from_pkg}::VERSION"};
  11         51  
267             }
268              
269 11 100       56 if ( ! $version ) {
270              
271 7         23 my $interface_hash = $Spec_for{$class}{interface};
272 7         48 my @code = (
273             "package $from_pkg;",
274             "our \$VERSION = 0.000002;",
275             "our \@ISA = ('$orig_pkg');",
276             "our \$Target;",
277             );
278              
279 7         17 foreach my $name (keys %{ $interface_hash }) {
  7         37  
280              
281 49         175 push @code, qq[
282             sub $name {
283             \$Target->can('$name')->(\@_);
284             }
285             ];
286             }
287 7     2   2417 eval join "\n", @code, '1;';
  2     52   138  
  52     11   4777  
  11     2   3137  
  2     13   1281  
  13     74   2842  
  74     13   5449  
  13         882  
288             }
289             }
290              
291             sub _validate_govern_options {
292 18     18   497 validate(@_, {
293             all => { type => BOOLEAN, optional => 1 },
294             pre => { type => BOOLEAN, optional => 1 },
295             post => { type => BOOLEAN, optional => 1 },
296             invariant => { type => BOOLEAN, optional => 1 },
297             emulate => { type => BOOLEAN, optional => 1 },
298             });
299             }
300              
301             1;
302              
303             __END__