File Coverage

blib/lib/Class/Classless/C3.pm
Criterion Covered Total %
statement 134 152 88.1
branch 46 66 69.7
condition 10 19 52.6
subroutine 23 28 82.1
pod 6 6 100.0
total 219 271 80.8


line stmt bran cond sub pod time code
1             package Class::Classless::C3;
2 1     1   79640 use 5.006;
  1         3  
  1         41  
3 1     1   6 use strict;
  1         1  
  1         34  
4 1     1   5 use warnings;
  1         6  
  1         58  
5             our $VERSION = '1.00';
6              
7 1     1   799 use Algorithm::C3;
  1         866  
  1         1169  
8              
9             # set this to a scalar ref for tracing
10             our $trace;
11              
12             # the root object
13             our $ROOT;
14              
15             # for caching results from Algorithm::C3::merge
16             our %c3cache;
17              
18             # Class::Classless::C3->new( ['name', method=>sub, ... ] )
19             # $classless->new( [ 'name', method => sub, ... ] )
20             # name recommended
21             sub new
22             {
23 9     9 1 650 my $parent = shift;
24 9   66     36 my $class = ref $parent || $parent;
25 9         26 my $self = bless {}, $class;
26             # Meta class is not subclassable this way....
27 9         29 $self->{_meta} = Class::Classless::C3::Meta->new();
28 9 100       20 $self->meta->parent(ref $parent ? $parent : $ROOT);
29 9         12 my $name = shift;
30 9         20 $self->meta->name($name);
31 9         47 $self->meta->addmethod( splice(@_,0,2) ) while @_;
32 9         49 $self->init($name,@_);
33 9         26 return $self;
34             }
35              
36             $ROOT = bless {}, 'Class::Classless::C3';
37             $ROOT->{_meta} = Class::Classless::C3::Meta->new();
38             $ROOT->meta->name('ROOT');
39             $ROOT->meta->addmethod( init => sub {} );
40              
41              
42             sub meta
43             {
44 54     54 1 981 return $_[0]->{_meta};
45             }
46              
47             our $AUTOLOAD;
48             # top level call
49             sub AUTOLOAD
50             {
51 14     14   27 my $self = $_[0];
52 14 0       29 my $sub = $self->can($AUTOLOAD) or
    50          
53             die("cannot call method ".($AUTOLOAD =~ m/([^:]*)$/g)[0]." on ".(ref($self)?$self->{_meta}->{name}:"'$self'"));
54 14 100       36 $$trace .= "called ".$self->{_meta}->name."->".($AUTOLOAD =~ m/([^:]*)$/g)[0]." (@_[1..$#_])\n" if ref $trace eq 'SCALAR';
55 14         43 goto $sub;
56             }
57              
58             # inherited call
59             sub NEXT
60             {
61 13     13 1 67 my $self = $_[0];
62 13         16 my $class;
63             my $method;
64 13         18 my $level = 1;
65 13         13 my $caller;
66             # caller is subname-ed to instance-name::method-name
67 13         83 while ($caller = (caller($level++))[3]) {
68 13         86 ($class,$method) = ($caller =~ m/^(.*)::([^:]+)$/s);
69 13 50       49 last unless $method =~ m/^(\(eval\)|__ANON__|DB::.*)$/;
70             }
71             # need to start from parent of owner of current method
72 13         31 my $sub = $self->can($method,from=>$class);
73 13 100       61 return unless $sub; # do not die on NEXT
74 8 100       27 $$trace .= "NEXT $method from $class\n" if ref $trace eq 'SCALAR';
75 8         29 goto $sub;
76             }
77              
78             sub VERSION
79 0     0 1 0 {
80             # stub
81             }
82              
83             sub isa
84             {
85 4     4 1 8 my $self = shift;
86 4         5 my $what = shift;
87              
88             my $c3 = $c3cache{$self->{_meta}->{name}} ||= [
89             Algorithm::C3::merge( $self,
90 0     0   0 sub { @{ $_[0]->{_meta}->{parents} } },
  0         0  
91 4   50     17 )];
92 4 100       11 if (ref $what) {
93 2 100       23 return grep($_ eq $what, @$c3) ? 1 : 0;
94             } else {
95 2 100       19 return grep($_->{_meta}->{name} eq $what, @$c3) ? 1 : 0;
96             }
97             }
98              
99             # this is here to avoid calling can('DESTROY') after meta is gone
100             sub DESTROY
101 0     0   0 {
102             }
103              
104             sub can
105             {
106 30     30 1 147 my $self = shift;
107 30         595 my $method = shift;
108 30         93 $method =~ s/^.*:://;
109 30 100 66     120 my $from = $_[0] && $_[0] eq 'from' ? $_[1] : undef;
110              
111 30 50       76 if (!$self->{_meta}) { warn("cannot can '$method' without meta"); }
  0         0  
112             my $c3 = $c3cache{$self->{_meta}->{name}} ||= [
113             Algorithm::C3::merge( $self,
114 38     38   772 sub { @{ $_[0]->{_meta}->{parents} } },
  38         155  
115 30   100     149 )];
116 30         1780 my $sub;
117 30         50 for my $o ( @$c3 ) {
118 86 100       152 if ($from) {
119 27 100       77 next if $o->{_meta}->{name} ne $from;
120 13         19 undef $from;
121 13         18 next;
122             }
123 59 50 33     253 if (ref $o && $o->{_meta}) {
124 59         120 $sub = $o->{_meta}->{methods}->{$method};
125 59 100       161 return $sub if $sub;
126             # for optional autoload-like behavior
127 37 50       90 if (ref $Class::Classless::C3::autoload eq 'CODE') {
128 0         0 $sub = $Class::Classless::C3::autoload->($o,$method);
129 0 0       0 return $sub if $sub;
130             }
131             } else {
132 0         0 $sub = UNIVERSAL::can($o,$method);
133 0 0       0 return $sub if $sub;
134             }
135             }
136             # catch methods defined in Class::Classless::C3
137 8         29 $sub = UNIVERSAL::can($self,$method);
138 8 100       20 return $sub if $sub;
139              
140 7         15 return undef;
141             }
142              
143             $Class::Classless::C3::autoload ||= '';
144              
145              
146             package # hide from pause
147             Class::Classless::C3::Meta;
148 1     1   887 use Sub::Name;
  1         757  
  1         671  
149              
150             $Class::Classless::C3::Meta::uid = 0;
151              
152             sub new
153             {
154 10     10   16 my $object = shift;
155 10   33     44 my $class = ref $object || $object;
156 10         32 my $self = bless {}, $class;
157 10         27 $self->init(@_);
158 10         32 return $self;
159             }
160              
161             sub init
162             {
163 10     10   12 my $self = shift;
164 10         38 $self->{parents} = [];
165             }
166              
167             sub name
168             {
169 32     32   38 my $self = shift;
170 32 100       66 if (@_) {
171 10 50       24 $self->purge_c3cache if $self->{name};
172 10         13 my $name = shift;
173 10         19 $self->{name} = $name;
174 10 100       22 unless ($self->{name}) {
175 1         4 $self->{name} = 'x_'.++$Class::Classless::C3::Meta::uid;
176             }
177 10         20 subname $name.'::'.$_ => $self->{methods}->{$_} for keys %{$self->{methods}};
  10         45  
178 10         29 $self->purge_c3cache;
179             }
180 32         140 return $self->{name};
181             }
182              
183             sub parent
184             {
185 10     10   13 my $self = shift;
186 10 100       26 if (@_) {
187             # clear any isa caching
188 9 50       11 $self->purge_c3cache if @{$self->{parents}};
  9         26  
189 9         13 my $par = shift;
190 9 50       23 die("called parent with nonref '$par'") unless ref $par;
191 9         22 $self->{parents} = [$par];
192             }
193 10         23 return $self->{parents}->[0];
194             }
195              
196             sub parents
197             {
198 1     1   3 my $self = shift;
199 1 50       5 if (@_) {
200 0 0       0 $self->purge_c3cache if $self->{parents};
201 0 0       0 if (ref $_[0] eq 'ARRAY') {
202 0         0 $self->{parents} = [@{$_[0]}];
  0         0  
203             } else {
204 0         0 $self->{parents} = [@_];
205             }
206             }
207             # return a copy of the array, so they cannot change our copy
208             # we need to clear the c3cache if our copy changes
209 1         3 return @{$self->{parents}};
  1         4  
210             }
211              
212             sub addparent
213             {
214 1     1   2 my $self = shift;
215 1         2 my $newp = shift;
216 1 50       4 return unless $newp;
217 1         4 $self->purge_c3cache;
218             # maybe this should unshift???
219 1         2 push @{ $self->{parents} }, $newp;
  1         5  
220             }
221              
222             sub addmethod
223             {
224 9     9   13 my $self = shift;
225 9         16 my($name,$sub) = @_;
226 9         20 my $fullname = $self->{name}.'::'.$name;
227 9         146 $self->{methods}->{$name} = subname $fullname => $sub;
228             }
229              
230             sub delmethod
231             {
232 0     0   0 my $self = shift;
233 0         0 my($name) = @_;
234 0         0 delete $self->{methods}->{$name};
235             }
236              
237             sub clone
238 0     0   0 {
239             }
240              
241             # creates a Classless object from an existing package
242             sub declassify
243             {
244 1     1   3 my $class = shift;
245 1         4 my $self = Class::Classless::C3->new($class);
246              
247 1     1   6 no strict 'refs';
  1         7  
  1         352  
248 1         2 my $symtable = \%{$class.'::'};
  1         5  
249 1         5 for my $sym ( keys %$symtable ) {
250 4 100       19 next if $sym =~ m/^(AUTOLOAD|NEXT|can|isa|VERSION|meta|new)$/;
251 3         4 my $sub = *{$symtable->{$sym}}{CODE};
  3         13  
252 3 100       12 if (defined $sub) {
253 1         4 $self->meta->addmethod($sym => $sub);
254 1         2 delete ${$class.'::'}{$sym}; #deletes all glob-parts
  1         7  
255             }
256             }
257 1         4 return $self;
258             }
259              
260             # clear any c3cache entries which contain this object
261             # (called when an object's parents change or object's name changes)
262             sub purge_c3cache
263             {
264 11     11   12 my $self = shift;
265 11   33     47 my $who = shift || $self->{name};
266 11         34 for my $k (keys %Class::Classless::C3::c3cache) {
267 45 100       49 if (grep $who eq $_->{_meta}->{name}, @{ $Class::Classless::C3::c3cache{$k} }) {
  45         212  
268 2         196 delete $Class::Classless::C3::c3cache{$k};
269             }
270             }
271             }
272              
273             sub show_c3cache # for debugging
274             {
275 2     2   4 my $self = shift;
276 11         19 return join ',',
277 2         6 map { $_->meta->name }
278 2         3 @{ $Class::Classless::C3::c3cache{$self->{name}} };
279             }
280              
281              
282             1;
283             __END__