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   21575 use strict;
  1         2  
  1         34  
3 1     1   919 use Class::C3;
  1         3787  
  1         8  
4 1     1   34 use base;
  1         8  
  1         138  
5             our $VERSION = '0.54';
6              
7             sub new
8             {
9 11     11 0 27135 my $class = shift;
10 11 50       34 $class = ref $class if ref $class;
11 11   50     53 $Class::Mixer::DEBUG ||= 0;
12              
13 11         59 $class->remix_class;
14              
15 10         20 my $self = bless {},$class;
16 10 100       128 $self->init(@_) if $self->can('init');
17 10         405 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 22 my $self = shift;
26 11   33     40 my $class = ref $self || $self;
27 1     1   5 no strict 'refs';
  1         8  
  1         356  
28 11 50       16 return if ${"$class\::REMIX"};
  11         75  
29              
30 11         14 ${"$class\::REMIX"} = 1;
  11         26  
31 11         14 @{"$class\::WASA"} = @{"$class\::ISA"};
  11         65  
  11         32  
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         17 my $con = {}; # constraints
40 11         73 $self->remix_collect($class,$con);
41              
42 11 50       32 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         63 $self->mixdown($con);
49 10 50       50 print qq{\@$class\::ISA = @{"$class\::ISA"}\n} if $Class::Mixer::DEBUG > 1;
  0         0  
50              
51 10         31 Class::C3::reinitialize();
52 10 50       586 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 285 my $self = shift;
61 203   33     723 my $class = ref $self || $self;
62 1     1   5 no strict 'refs';
  1         2  
  1         268  
63              
64 203         283 my $subclass = shift;
65 203         276 my $con = shift;
66 203 100       688 return if exists $con->{$subclass};
67 90         212 $con->{$subclass} = {};
68              
69 90         335 my @ISA = @{"$subclass\::WASA"} ?
70 14         46 @{"$subclass\::WASA"} :
71 90 100       124 @{"$subclass\::ISA"};
  76         249  
72 90         115 my @mixers = @{"$subclass\::MIXERS"};
  90         281  
73              
74 90         132 my $type = 'before';
75 90         188 for my $mixer (@ISA,@mixers) {
76 269 100       798 if ($mixer =~ m/^(before|after|isa|requires?|optional)$/) {
77 77         103 $type = $mixer;
78 77 50       162 $type = 'requires' if $type eq 'require';
79 77         124 next;
80             }
81              
82 192   100     1161 $con->{$subclass}->{$type} ||= [];
83 192         235 push @{$con->{$subclass}->{$type}}, $mixer;
  192         506  
84              
85 192         354 remix_require($mixer);
86 192         527 $self->remix_collect($mixer,$con);
87             }
88             }
89              
90              
91             # "borrowed" from base.pm
92             sub remix_require
93             {
94 1     1   5 no strict 'refs';
  1         2  
  1         241  
95 225     225 0 323 my $base = shift;
96              
97 225         241 my $vglob = ${$base.'::'}{VERSION};
  225         711  
98 225 100 100     1108 if ($vglob && *$vglob{SCALAR}) {
99 0         0 ${$base.'::VERSION'} = '-1, set by Class::Mixer'
100 198 50       250 unless defined ${$base.'::VERSION'};
  198         870  
101             } else {
102 27         104 local $SIG{__DIE__};
103 27         1473 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     350 die $@ if $@ && $@ !~ /^Can't locate .*? at \(eval /;
107 27 100       38 unless (%{"$base\::"}) {
  27         127  
108 1         5 require Carp;
109 1         203 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         167 ${$base.'::VERSION'} = "-1, set by Class::Mixer"
116 26 50       39 unless defined ${$base.'::VERSION'};
  26         147  
117             }
118             }
119              
120              
121             sub mixdown
122             {
123 11     11 0 16 my $self = shift;
124 11   33     41 my $class = ref $self || $self;
125 11         17 my $con = shift;
126 1     1   5 no strict 'refs';
  1         2  
  1         821  
127              
128 11         75 my @classes = ($class, grep $_ ne $class, keys %$con);
129 11         20 my @BEA = @{"$class\::ISA"};
  11         40  
130              
131             # isa: when A isa B,
132             # substitute A for all B's
133 11         17 for my $subclass (@classes) {
134 90 100       207 next unless $con->{$subclass}->{isa};
135 9         15 for my $isa (@{$con->{$subclass}->{isa}}) {
  9         20  
136 9         20 for my $sub2 (@classes) {
137 106         116 for my $k (keys %{$con->{$sub2}}) {
  106         231  
138 137 100       273 next if $k eq 'isa';
139 120         129 for (@{$con->{$sub2}->{$k}}) {
  120         223  
140 233 100       545 $_ = $subclass if $_ eq $isa;
141             }
142             }
143             }
144             }
145             }
146              
147 11         15 for my $subclass (@classes) {
148 90 100       193 next unless $con->{$subclass}->{optional};
149 3         5 my @opt = @{$con->{$subclass}->{optional}};
  3         9  
150 3         7 $con->{$subclass}->{optional} = {};
151 3         38 for my $o (@opt) {
152 3         10 $con->{$subclass}->{optional}->{$o} = 1;
153             }
154             }
155             # after: A after B means B before A, A is optional
156 11         16 for my $subclass (@classes) {
157 90 100       190 next unless $con->{$subclass}->{after};
158 6         10 for my $mixer (@{$con->{$subclass}->{after}}) {
  6         14  
159 6   50     15 $con->{$mixer}->{before} ||= [];
160 6         8 push @{$con->{$mixer}->{before}}, $subclass;
  6         11  
161 6         19 $con->{$mixer}->{optional}->{$subclass} = 1;
162             }
163             }
164 11 50       23 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         20 for my $subclass (@classes) {
171             $con->{$subclass}->{node} = {
172 90         312 class=>$subclass,
173             isa=>[],
174             bef=>[],
175             req=>[],
176             };
177             }
178 11         19 for my $subclass (@classes) {
179 90         154 push @{$con->{$subclass}->{node}->{req}},
180 11         31 map { $con->{$_}->{node} }
181 90         99 @{$con->{$subclass}->{requires}};
  90         186  
182 90         158 push @{$con->{$subclass}->{node}->{bef}},
183 169         342 map { $con->{$_}->{node} }
184 90         110 @{$con->{$subclass}->{before}};
  90         151  
185             # isa should bind tightest
186 90         143 push @{$con->{$subclass}->{node}->{isa}},
187 9         24 map { $con->{$_}->{node} }
188 90         115 @{$con->{$subclass}->{isa}};
  90         205  
189             ;
190             }
191 11 50       25 if ($Class::Mixer::DEBUG > 4) {
192 0         0 print Data::Dumper::Dumper($con->{$class}->{node});
193             }
194              
195             # reverse depth first traversal
196 11         30 @BEA = depth_first_traverse($con->{$class}->{node});
197 10         23 shift @BEA; # remove self
198              
199 10         16 @{"$class\::ISA"} = @BEA;
  10         330  
200             }
201              
202             sub depth_first_traverse
203             {
204 198     198 0 234 my $node = shift;
205 198   100     426 my $stem = shift || '';
206 198         362 $stem = $stem.' '.$node->{class}.' ';
207             #print "$stem\n";
208              
209             # check for loops
210 198         212 for (@{$node->{bef}},@{$node->{isa}}) {
  198         299  
  198         367  
211 256 100       2078 if ($stem =~ m/\s$$_{class}\s/) {
212 1         17 die("inconsistent hierarchy ($stem $$_{class})");
213             }
214             }
215              
216 197 100       492 return if $node->{visited};
217 89         144 $node->{visited} = 1;
218 89         106 my @r;
219              
220 89         104 for (@{$node->{req}}, @{$node->{bef}}, @{$node->{isa}}) {
  89         133  
  89         142  
  89         173  
221 187         333 unshift @r,depth_first_traverse($_,$stem);
222             }
223             #print $node->{class};
224             #print " ";
225 87         399 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   24763 my $pkg = shift;
236 27 50       71 return unless $pkg eq 'Class::Mixer'; # not for inheritors
237 27         48 my $class = caller(0);
238              
239             # save off classes -- real work done in new()
240 1     1   5 no strict 'refs';
  1         2  
  1         30  
241 1     1   4 no warnings 'once';
  1         2  
  1         234  
242 27         46 my @mixers = @{"$class\::MIXERS"} = @_;
  27         174  
243              
244             # require references classes
245 27         40 my $type = 'before';
246 27         45 for my $mixer (@mixers) {
247 65 100       237 if ($mixer =~ m/^(before|after|isa|requires?|optional)$/) {
248 31         41 $type = $mixer;
249 31 50       66 $type = 'requires' if $type eq 'require';
250 31         52 next;
251             }
252              
253 34 100       112 remix_require($mixer) unless $type eq 'optional';
254             }
255              
256             # force Class::Mixer into ISA, so our new() will be invoked
257 26         32 push @{"$class\::ISA"}, $pkg;
  26         241  
258              
259             # from Class::C3::import
260 26 100       71 if ($class ne 'main') {
261 25 50       103 mro::set_mro($class, 'c3') if $Class::C3::C3_IN_CORE;
262 25 50       1572 $Class::C3::MRO{$class} = undef unless exists $Class::C3::MRO{$class};
263             }
264             }
265              
266             1;
267              
268             __END__