File Coverage

blib/lib/Class/Classless/C3.pm
Criterion Covered Total %
statement 133 150 88.6
branch 47 70 67.1
condition 10 19 52.6
subroutine 23 27 85.1
pod 5 5 100.0
total 218 271 80.4


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