File Coverage

blib/lib/Class/Mixer.pm
Criterion Covered Total %
statement 177 190 93.1
branch 48 64 75.0
condition 12 22 54.5
subroutine 16 16 100.0
pod 0 6 0.0
total 253 298 84.9


line stmt bran cond sub pod time code
1             package Class::Mixer;
2 1     1   34081 use strict;
  1         2  
  1         37  
3 1     1   947 use Class::C3;
  1         9282  
  1         11  
4 1     1   49 use base;
  1         9  
  1         173  
5             our $VERSION = '0.52';
6              
7             sub new
8             {
9 11     11 0 23456 my $class = shift;
10 11 50       33 $class = ref $class if ref $class;
11 11   50     54 $Class::Mixer::DEBUG ||= 0;
12              
13 11         90 $class->remix_class;
14              
15 10         23 my $self = bless {},$class;
16 10 100       163 $self->init(@_) if $self->can('init');
17 10         462 return $self;
18             }
19              
20              
21             # this will remix the class the first time it is instantiated,
22             # after that, the class is considered closed.
23             sub remix_class
24             {
25 11     11 0 20 my $self = shift;
26 11   33     41 my $class = ref $self || $self;
27 1     1   6 no strict 'refs';
  1         2  
  1         372  
28 11 50       15 return if ${"$class\::REMIX"};
  11         68  
29              
30 11         14 ${"$class\::REMIX"} = 1;
  11         24  
31 11         17 @{"$class\::WASA"} = @{"$class\::ISA"};
  11         58  
  11         34  
32              
33 11 50       28 if ($Class::Mixer::DEBUG) {
34 0         0 print "REMIXING $class...\n";
35 0         0 my @classes = Class::C3::calculateMRO($class);
36 0         0 print "before: @classes\n";
37             }
38              
39 11         18 my $con = {}; # constraints
40 11         77 $self->remix_collect($class,$con);
41              
42 11 50       33 if ($Class::Mixer::DEBUG > 2) {
43 0         0 require Data::Dumper;
44 0         0 $Data::Dumper::Sortkeys = 1;
45 0         0 print Data::Dumper::Dumper($con);
46             }
47              
48 11         70 $self->mixdown($con);
49 10 50       50 print qq{\@$class\::ISA = @{"$class\::ISA"}\n} if $Class::Mixer::DEBUG > 1;
  0         0  
50              
51 10         34 Class::C3::reinitialize();
52 10 50       845 if ($Class::Mixer::DEBUG) {
53 0         0 my @classes = Class::C3::calculateMRO($class);
54 0         0 print "after: @classes\n";
55             }
56             }
57              
58             sub remix_collect
59             {
60 203     203 0 207 my $self = shift;
61 203   33     595 my $class = ref $self || $self;
62 1     1   6 no strict 'refs';
  1         4  
  1         977  
63              
64 203         202 my $subclass = shift;
65 203         179 my $con = shift;
66 203 100       610 return if exists $con->{$subclass};
67 90         191 $con->{$subclass} = {};
68              
69 90         334 my @ISA = defined(@{"$subclass\::WASA"}) ?
  14         48  
70 76         238 @{"$subclass\::WASA"} :
71 90 100       93 @{"$subclass\::ISA"};
72 90         94 my @mixers = @{"$subclass\::MIXERS"};
  90         255  
73              
74 90         96 my $type = 'before';
75 90         175 for my $mixer (@ISA,@mixers) {
76 269 100       817 if ($mixer =~ m/^(before|after|isa|requires?|optional)$/) {
77 77         78 $type = $mixer;
78 77 50       132 $type = 'requires' if $type eq 'require';
79 77         93 next;
80             }
81              
82 192   100     790 $con->{$subclass}->{$type} ||= [];
83 192         197 push @{$con->{$subclass}->{$type}}, $mixer;
  192         389  
84              
85 192         294 remix_require($mixer);
86 192         453 $self->remix_collect($mixer,$con);
87             }
88             }
89              
90              
91             # "borrowed" from base.pm
92             sub remix_require
93             {
94 1     1   277 no strict 'refs';
  1         6  
  1         617  
95 225     225 0 251 my $base = shift;
96              
97 225         212 my $vglob = ${$base.'::'}{VERSION};
  225         599  
98 225 100 100     916 if ($vglob && *$vglob{SCALAR}) {
99 0         0 ${$base.'::VERSION'} = '-1, set by Class::Mixer'
  198         772  
100 198 50       179 unless defined ${$base.'::VERSION'};
101             } else {
102 27         227 local $SIG{__DIE__};
103 27         2050 eval "require $base";
104             # Only ignore "Can't locate" errors from our eval require.
105             # Other fatal errors (syntax etc) must be reported.
106 27 50 33     448 die $@ if $@ && $@ !~ /^Can't locate .*? at \(eval /;
107 27 100       38 unless (%{"$base\::"}) {
  27         141  
108 1         5 require Carp;
109 1         221 Carp::croak(<
110             Base class package "$base" is empty.
111             (Perhaps you need to 'use' the module which defines that package first.)
112             ERROR
113              
114             }
115 26         204 ${$base.'::VERSION'} = "-1, set by Class::Mixer"
  26         153  
116 26 50       38 unless defined ${$base.'::VERSION'};
117             }
118             }
119              
120              
121             sub mixdown
122             {
123 11     11 0 14 my $self = shift;
124 11   33     39 my $class = ref $self || $self;
125 11         15 my $con = shift;
126 1     1   7 no strict 'refs';
  1         1  
  1         1097  
127              
128 11         93 my @classes = ($class, grep $_ ne $class, keys %$con);
129 11         18 my @BEA = @{"$class\::ISA"};
  11         42  
130              
131             # isa: when A isa B,
132             # substitute A for all B's
133 11         17 for my $subclass (@classes) {
134 90 100       199 next unless $con->{$subclass}->{isa};
135 9         12 for my $isa (@{$con->{$subclass}->{isa}}) {
  9         21  
136 9         11 for my $sub2 (@classes) {
137 106         186 for my $k (keys %{$con->{$sub2}}) {
  106         232  
138 137 100       232 next if $k eq 'isa';
139 120         105 for (@{$con->{$sub2}->{$k}}) {
  120         206  
140 233 100       577 $_ = $subclass if $_ eq $isa;
141             }
142             }
143             }
144             }
145             }
146              
147 11         20 for my $subclass (@classes) {
148 90 100       188 next unless $con->{$subclass}->{optional};
149 3         5 my @opt = @{$con->{$subclass}->{optional}};
  3         8  
150 3         8 $con->{$subclass}->{optional} = {};
151 3         56 for my $o (@opt) {
152 3         13 $con->{$subclass}->{optional}->{$o} = 1;
153             }
154             }
155             # after: A after B means B before A, A is optional
156 11         17 for my $subclass (@classes) {
157 90 100       183 next unless $con->{$subclass}->{after};
158 6         6 for my $mixer (@{$con->{$subclass}->{after}}) {
  6         14  
159 6   50     17 $con->{$mixer}->{before} ||= [];
160 6         7 push @{$con->{$mixer}->{before}}, $subclass;
  6         12  
161 6         23 $con->{$mixer}->{optional}->{$subclass} = 1;
162             }
163             }
164 11 50       27 if ($Class::Mixer::DEBUG > 5) {
165 0         0 print "AFTER isa and after substitutions\n";
166 0         0 print Data::Dumper::Dumper($con);
167             }
168              
169             # make a tree
170 11         14 for my $subclass (@classes) {
171 90         356 $con->{$subclass}->{node} = {
172             class=>$subclass,
173             isa=>[],
174             bef=>[],
175             req=>[],
176             };
177             }
178 11         22 for my $subclass (@classes) {
179 90         140 push @{$con->{$subclass}->{node}->{req}},
  11         24  
180 90         187 map { $con->{$_}->{node} }
181 90         85 @{$con->{$subclass}->{requires}};
182 90         138 push @{$con->{$subclass}->{node}->{bef}},
  169         344  
183 90         140 map { $con->{$_}->{node} }
184 90         91 @{$con->{$subclass}->{before}};
185             # isa should bind tightest
186 90         152 push @{$con->{$subclass}->{node}->{isa}},
  9         22  
187 90         203 map { $con->{$_}->{node} }
188 90         99 @{$con->{$subclass}->{isa}};
189             ;
190             }
191 11 50       28 if ($Class::Mixer::DEBUG > 4) {
192 0         0 print Data::Dumper::Dumper($con->{$class}->{node});
193             }
194              
195             # reverse depth first traversal
196 11         29 @BEA = depth_first_traverse($con->{$class}->{node});
197 10         27 shift @BEA; # remove self
198              
199 10         14 @{"$class\::ISA"} = @BEA;
  10         469  
200             }
201              
202             sub depth_first_traverse
203             {
204 198     198 0 210 my $node = shift;
205 198   100     417 my $stem = shift || '';
206 198         365 $stem = $stem.' '.$node->{class}.' ';
207             #print "$stem\n";
208              
209             # check for loops
210 198         182 for (@{$node->{bef}},@{$node->{isa}}) {
  198         267  
  198         349  
211 256 100       2299 if ($stem =~ m/\s$$_{class}\s/) {
212 1         19 die("inconsistent hierarchy ($stem $$_{class})");
213             }
214             }
215              
216 197 100       570 return if $node->{visited};
217 89         141 $node->{visited} = 1;
218 89         87 my @r;
219              
220 89         93 for (@{$node->{req}}, @{$node->{bef}}, @{$node->{isa}}) {
  89         133  
  89         112  
  89         146  
221 187         302 unshift @r,depth_first_traverse($_,$stem);
222             }
223             #print $node->{class};
224             #print " ";
225 87         377 return $node->{class},@r;
226             }
227              
228              
229             # use Class::Mixer automatically adds Class::Mixer to ISA
230             # require all reference classes ala use base
231             # XXX test: do not require optional classes
232             # also force c3 semantics ala use Class::C3
233             sub import
234             {
235 27     27   43482 my $pkg = shift;
236 27 50       90 return unless $pkg eq 'Class::Mixer'; # not for inheritors
237 27         51 my $class = caller(0);
238              
239             # save off classes -- real work done in new()
240 1     1   15 no strict 'refs';
  1         2  
  1         569  
241 1     1   69 no warnings 'once';
  1         2  
  1         1310  
242 27         45 my @mixers = @{"$class\::MIXERS"} = @_;
  27         260  
243              
244             # require references classes
245 27         38 my $type = 'before';
246 27         51 for my $mixer (@mixers) {
247 65 100       251 if ($mixer =~ m/^(before|after|isa|requires?|optional)$/) {
248 31         47 $type = $mixer;
249 31 50       121 $type = 'requires' if $type eq 'require';
250 31         55 next;
251             }
252              
253 34 100       107 remix_require($mixer) unless $type eq 'optional';
254             }
255              
256             # force Class::Mixer into ISA, so our new() will be invoked
257 26         40 push @{"$class\::ISA"}, $pkg;
  26         815  
258              
259             # from Class::C3::import
260 26 100       214 if ($class ne 'main') {
261 25 50       140 mro::set_mro($class, 'c3') if $Class::C3::C3_IN_CORE;
262 25 50       2524 $Class::C3::MRO{$class} = undef unless exists $Class::C3::MRO{$class};
263             }
264             }
265              
266             1;
267              
268             __END__