File Coverage

blib/lib/Bio/Phylo/Util/MOP.pm
Criterion Covered Total %
statement 140 224 62.5
branch 12 30 40.0
condition 3 12 25.0
subroutine 40 60 66.6
pod 0 27 0.0
total 195 353 55.2


line stmt bran cond sub pod time code
1             package Bio::Phylo::Util::MOP;
2 57     57   339 use strict;
  57         107  
  57         1517  
3 57     57   18434 use attributes;
  57         55290  
  57         312  
4 57     57   21795 use Attribute::Handlers;
  57         143646  
  57         346  
5 57     57   5210 use Data::Dumper;
  57         44734  
  57         3003  
6 57     57   3223 use Bio::Phylo::Util::Exceptions 'throw';
  57         123  
  57         2428  
7 57     57   4458 use Bio::Phylo::Util::Logger ':levels';
  57         123  
  57         6820  
8 57     57   363 use Scalar::Util qw( refaddr );
  57         116  
  57         4687  
9              
10             =head1 NAME
11              
12             Bio::Phylo::Util::MOP - Meta-object programming, no serviceable parts inside
13              
14             =cut
15              
16             # this will be populated when the attribute handlers are triggered
17             my %methods;
18              
19             # this will progressively store/memoize all superclasses for given classes
20             my %classes;
21              
22             # this will progressively store/memoize the methods for given classes
23             my %class_methods;
24              
25             # this might be used to check the interface of alien subclasses
26       235     sub import {
27            
28             }
29              
30             # my %sims = $mop->get_symtable('Bio::Phylo');
31             sub get_symtable {
32 0     0 0 0 my ( $self, $package ) = @_;
33 0         0 my %symtable;
34             {
35 57     57   344 no strict 'refs';
  57         118  
  57         2452  
  0         0  
36 0         0 %symtable = %{"${package}::"};
  0         0  
37 57     57   292 use strict;
  57         123  
  57         3134  
38             }
39 0         0 return \%symtable;
  0         0  
40             }
41              
42             # $mop->get_method('Bio::Phylo::new')->()
43             sub get_method {
44 1852     1852 0 2736 my ( $self, $fqn ) = @_;
45 1852         1943 my $coderef;
46 0         0 eval {
47 57     57   308 no strict 'refs';
  57         121  
  57         2263  
48 1852         2015 $coderef = \&{"${fqn}"};
  1852         5056  
49 57     57   289 use strict;
  57         110  
  57         12058  
50 1852         2091 };
51 1852         3247 return $coderef;
52             }
53              
54             # @methods = @{ $mop->get_implementations( 'new', $obj || $package ) };
55             sub get_implementations {
56 0     0 0 0 my ( $self, $method, $obj ) = @_;
57 0         0 my @methods = grep { $_->{'name'} eq $method } @{ $self->get_methods($obj) };
  0         0  
  0         0  
58 0         0 return \@methods;
59             }
60              
61             # my @classes = @{ $mop->get_classes($obj) }
62             sub get_classes {
63 12370     12370 0 20099 my ( $self, $obj, $all ) = @_;
64 12370   33     23503 my $class = ref $obj || $obj;
65            
66             # return if already cached
67 12370 100       20693 if ( $classes{$class} ) {
68 12147         37888 return $classes{$class};
69             }
70            
71             # compute, cache, return
72             else {
73 223         630 my ( $seen, $isa ) = ( {}, [] );
74 223         723 _recurse_isa($class, $isa, $seen, $all);
75 223         469 $classes{$class} = $isa;
76 223         1183 return $isa;
77             }
78             }
79              
80             # starting from $class, push all superclasses (+$class) into @$isa,
81             # %$seen is just a helper to avoid getting stuck in cycles
82             sub _recurse_isa {
83 1752     1752   2953 my ( $class, $isa, $seen, $all ) = @_;
84 1752 50       3424 if ( not $seen->{$class} ) {
85 1752         3001 $seen->{$class} = 1;
86 1752 50 33     5330 if ( ( $class ne 'Exporter' and $class ne 'DynaLoader' ) or $all ) {
      33        
87 1752         2968 push @{$isa}, $class;
  1752         3016  
88             }
89 1752         2282 my @isa;
90             {
91 57     57   367 no strict 'refs';
  57         117  
  57         2220  
  0         0  
92 1752         1950 @isa = @{"${class}::ISA"};
  1752         6689  
93 57     57   290 use strict;
  57         109  
  57         35560  
94             }
95 1752         1990 _recurse_isa( $_, $isa, $seen, $all ) for @isa;
  1752         4750  
96             }
97             }
98              
99             # my @methods = @{ $mop->get_methods($obj) };
100             sub get_methods {
101 0     0 0 0 my ( $self, $obj ) = @_;
102 0   0     0 my $class = ref $obj || $obj;
103            
104             # return if already cached
105 0 0       0 if ( $class_methods{$class} ) {
106 0         0 return $class_methods{$class};
107             }
108            
109             # compute, cache, return
110             else {
111 0         0 my $isa = $self->get_classes($obj);
112 0         0 my @methods;
113 0         0 for my $package ( @{ $isa } ) {
  0         0  
114              
115 0         0 my %symtable = %{ $self->get_symtable($package) };
  0         0  
116            
117             # at this point we have lots of things, we just want methods
118 0         0 for my $entry ( keys %symtable ) {
119            
120             # check if entry is a CODE reference
121 0         0 my $can = $package->can( $entry );
122 0 0       0 if ( ref $can eq 'CODE' ) {
123             push @methods, {
124             'package' => $package,
125             'name' => $entry,
126 0         0 'glob' => $symtable{$entry},
127             'code' => $can,
128             };
129             }
130             }
131             }
132 0         0 $class_methods{$class} = \@methods;
133 0         0 return \@methods;
134             }
135             }
136              
137             sub get_methods_by_attribute {
138 326     326 0 496 my ( $self, $obj, $attribute ) = @_;
139 326         569 my $isa = $self->get_classes($obj);
140 326         530 my $methods = $methods{$attribute};
141 326         425 my @return;
142 326         370 for my $class ( @{ $isa } ) {
  326         587  
143 2438 100       4236 if ( $methods->{$class} ) {
144 807         914 for my $key ( keys %{ $methods->{$class} } ) {
  807         1688  
145             push @return, {
146             'package' => $class,
147             'name' => $key,
148 2205         5456 'code' => $methods->{$class}->{$key}
149             };
150             }
151             }
152             }
153 326         764 return \@return;
154             }
155              
156             sub get_accessors {
157 0     0 0 0 my ( $self, $obj ) = @_;
158 0         0 return $self->get_methods_by_attribute($obj,'Accessor');
159             }
160              
161             sub get_mutators {
162 0     0 0 0 my ( $self, $obj ) = @_;
163 0         0 return $self->get_methods_by_attribute($obj,'Mutator');
164             }
165              
166             sub get_abstracts {
167 0     0 0 0 my ( $self, $obj ) = @_;
168 0         0 return $self->get_methods_by_attribute($obj,'Abstract');
169             }
170              
171             sub get_constructors {
172 109     109 0 190 my ( $self, $obj ) = @_;
173 109         228 return $self->get_methods_by_attribute($obj,'Constructor');
174             }
175              
176             sub get_clonables {
177 109     109 0 187 my ( $self, $obj ) = @_;
178 109         197 return $self->get_methods_by_attribute($obj,'Clonable');
179             }
180              
181             sub get_deep_clonables {
182 108     108 0 171 my ( $self, $obj ) = @_;
183 108         218 return $self->get_methods_by_attribute($obj,'DeepClonable');
184             }
185              
186             sub get_destructors {
187 0     0 0 0 my ( $self, $obj ) = @_;
188 0         0 return $self->get_methods_by_attribute($obj,'Destructor');
189             }
190              
191             sub get_privates {
192 0     0 0 0 my ( $self, $obj ) = @_;
193 0         0 return $self->get_methods_by_attribute($obj,'Private');
194             }
195              
196             sub get_statics {
197 0     0 0 0 my ( $self, $obj ) = @_;
198 0         0 return $self->get_methods_by_attribute($obj,'Static');
199             }
200              
201             sub get_serializers {
202 0     0 0 0 my ( $self, $obj ) = @_;
203 0         0 return $self->get_methods_by_attribute($obj,'Serializer');
204             }
205              
206             sub _handler {
207 1886     1886   2860 eval {
208 1886         3470 my ($package, $symbol, $referent, $attr, $data) = @_;
209 1886 50       5004 return if $symbol eq 'ANON';
210 1886         4907 my $name = *$symbol;
211 1886         14981 $name =~ s/.*://;
212 1886 100       6134 $methods{$attr} = {} unless $methods{$attr};
213 1886 100       4916 $methods{$attr}->{$package} = {} unless $methods{$attr}->{$package};
214 1886         4653 $methods{$attr}->{$package}->{$name} = $referent;
215             };
216 1886 50       5386 if ( $@ ) {
217 0           throw 'API' => $@;
218             }
219             }
220              
221             sub UNIVERSAL::Accessor : ATTR(CODE) {
222 0     0 0 0 my ($package, $symbol, $referent, $attr, $data) = @_;
223 0         0 _handler(@_);
224 57     57   402 }
  57         116  
  57         308  
225              
226             sub UNIVERSAL::Private : ATTR(CODE) {
227 0     0 0 0 my ($package, $symbol, $referent, $attr, $data) = @_;
228 57     57   15581 no warnings 'redefine';
  57         114  
  57         8337  
229 0 0       0 return if $symbol eq 'ANON';
230             *$symbol = sub {
231 0     0   0 my ($calling_package) = caller;
232 0         0 my $symname = *$symbol;
233 0         0 $symname =~ s/^\*//;
234 0         0 $symname =~ s/::[^:]+$//;
235 0 0       0 if ( $symname ne $package ) {
236 0         0 throw 'API' => "Attempt to call Private method from outside package";
237             }
238 0         0 $referent->(@_);
239 0         0 };
240 0         0 _handler(@_);
241 57     57   373 }
  57         124  
  57         243  
242              
243             sub UNIVERSAL::Protected : ATTR(CODE) {
244 0     0 0 0 my ($package, $symbol, $referent, $attr, $data) = @_;
245 57     57   13976 no warnings 'redefine';
  57         118  
  57         10916  
246 0 0       0 return if $symbol eq 'ANON';
247             *$symbol = sub {
248 0     0   0 my ($calling_package) = caller;
249 0         0 my $symname = *$symbol;
250 0         0 my $method = $symname;
251 0         0 $symname =~ s/^\*//;
252 0         0 $symname =~ s/::[^:]+$//;
253 0         0 my @package_names = split /::/, $package;
254 0         0 my @calling_names = split /::/, $calling_package;
255 0         0 my $seen_class = $package_names[0] eq $calling_names[0];
256 0 0       0 if ( not $seen_class ) {
257 0         0 throw 'API' => "Attempt to call Protected method $method from outside of top-level namespace";
258             }
259 0         0 $referent->(@_);
260 0         0 };
261 0         0 _handler(@_);
262 57     57   380 }
  57         111  
  57         257  
263              
264             sub UNIVERSAL::Constructor : ATTR(CODE) {
265 180     180 0 287492 my ($package, $symbol, $referent, $attr, $data) = @_;
266 180         583 _handler(@_);
267 57     57   15709 }
  57         120  
  57         201  
268              
269             sub UNIVERSAL::Destructor : ATTR(CODE) {
270 272     272 0 102388 my ($package, $symbol, $referent, $attr, $data) = @_;
271 272         786 _handler(@_);
272 57     57   15306 }
  57         128  
  57         207  
273              
274             sub UNIVERSAL::Static : ATTR(CODE) {
275 0     0 0 0 my ($package, $symbol, $referent, $attr, $data) = @_;
276 0         0 _handler(@_);
277 57     57   15942 }
  57         127  
  57         211  
278              
279             sub UNIVERSAL::Mutator : ATTR(CODE) {
280 0     0 0 0 my ($package, $symbol, $referent, $attr, $data) = @_;
281 0         0 _handler(@_);
282 57     57   16886 }
  57         130  
  57         205  
283              
284             sub UNIVERSAL::Abstract : ATTR(CODE) {
285 0     0 0 0 my ($package, $symbol, $referent, $attr, $data) = @_;
286 0         0 _handler(@_);
287 0 0       0 return if $symbol eq 'ANON';
288 57     57   16107 no warnings 'redefine';
  57         118  
  57         3917  
289 0     0   0 *$symbol = sub { throw 'NotImplemented' => "Abstract method, can't call $symbol" };
  0         0  
290 57     57   314 }
  57         102  
  57         209  
291              
292             sub UNIVERSAL::Clonable : ATTR(CODE) {
293 1316     1316 0 489165 my ($package, $symbol, $referent, $attr, $data) = @_;
294 1316         3354 _handler(@_);
295 57     57   16272 }
  57         131  
  57         240  
296              
297             sub UNIVERSAL::DeepClonable : ATTR(CODE) {
298 118     118 0 6255 my ($package, $symbol, $referent, $attr, $data) = @_;
299 118         326 _handler(@_);
300 57     57   17178 }
  57         154  
  57         216  
301              
302             sub UNIVERSAL::Serializer : ATTR(CODE) {
303 0     0 0 0 my ($package, $symbol, $referent, $attr, $data) = @_;
304 0         0 _handler(@_);
305 57     57   17484 }
  57         168  
  57         206  
306              
307             1;
308              
309