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   325 use strict;
  57         103  
  57         1442  
3 57     57   17605 use attributes;
  57         52685  
  57         285  
4 57     57   20391 use Attribute::Handlers;
  57         137815  
  57         280  
5 57     57   5518 use Data::Dumper;
  57         51327  
  57         2885  
6 57     57   3254 use Bio::Phylo::Util::Exceptions 'throw';
  57         126  
  57         2407  
7 57     57   4587 use Bio::Phylo::Util::Logger ':levels';
  57         128  
  57         6449  
8 57     57   355 use Scalar::Util qw( refaddr );
  57         111  
  57         4502  
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   324 no strict 'refs';
  57         123  
  57         2407  
  0         0  
36 0         0 %symtable = %{"${package}::"};
  0         0  
37 57     57   286 use strict;
  57         108  
  57         3158  
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 2918 my ( $self, $fqn ) = @_;
45 1852         2090 my $coderef;
46 0         0 eval {
47 57     57   291 no strict 'refs';
  57         109  
  57         2133  
48 1852         2007 $coderef = \&{"${fqn}"};
  1852         5304  
49 57     57   290 use strict;
  57         114  
  57         11452  
50 1852         2233 };
51 1852         3566 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 14950     14950 0 23722 my ( $self, $obj, $all ) = @_;
64 14950   33     28604 my $class = ref $obj || $obj;
65            
66             # return if already cached
67 14950 100       25126 if ( $classes{$class} ) {
68 14727         47903 return $classes{$class};
69             }
70            
71             # compute, cache, return
72             else {
73 223         509 my ( $seen, $isa ) = ( {}, [] );
74 223         660 _recurse_isa($class, $isa, $seen, $all);
75 223         433 $classes{$class} = $isa;
76 223         1064 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   2668 my ( $class, $isa, $seen, $all ) = @_;
84 1752 50       3109 if ( not $seen->{$class} ) {
85 1752         2618 $seen->{$class} = 1;
86 1752 50 33     5204 if ( ( $class ne 'Exporter' and $class ne 'DynaLoader' ) or $all ) {
      33        
87 1752         2609 push @{$isa}, $class;
  1752         2809  
88             }
89 1752         2076 my @isa;
90             {
91 57     57   339 no strict 'refs';
  57         106  
  57         2066  
  0         0  
92 1752         1797 @isa = @{"${class}::ISA"};
  1752         6340  
93 57     57   281 use strict;
  57         98  
  57         34626  
94             }
95 1752         1861 _recurse_isa( $_, $isa, $seen, $all ) for @isa;
  1752         4491  
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 534 my ( $self, $obj, $attribute ) = @_;
139 326         562 my $isa = $self->get_classes($obj);
140 326         553 my $methods = $methods{$attribute};
141 326         424 my @return;
142 326         385 for my $class ( @{ $isa } ) {
  326         551  
143 2438 100       4397 if ( $methods->{$class} ) {
144 807         936 for my $key ( keys %{ $methods->{$class} } ) {
  807         1812  
145             push @return, {
146             'package' => $class,
147             'name' => $key,
148 2205         5950 'code' => $methods->{$class}->{$key}
149             };
150             }
151             }
152             }
153 326         909 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 184 my ( $self, $obj ) = @_;
173 109         267 return $self->get_methods_by_attribute($obj,'Constructor');
174             }
175              
176             sub get_clonables {
177 109     109 0 194 my ( $self, $obj ) = @_;
178 109         224 return $self->get_methods_by_attribute($obj,'Clonable');
179             }
180              
181             sub get_deep_clonables {
182 108     108 0 195 my ( $self, $obj ) = @_;
183 108         223 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   3001 eval {
208 1886         3168 my ($package, $symbol, $referent, $attr, $data) = @_;
209 1886 50       4650 return if $symbol eq 'ANON';
210 1886         4689 my $name = *$symbol;
211 1886         14382 $name =~ s/.*://;
212 1886 100       5789 $methods{$attr} = {} unless $methods{$attr};
213 1886 100       4711 $methods{$attr}->{$package} = {} unless $methods{$attr}->{$package};
214 1886         4507 $methods{$attr}->{$package}->{$name} = $referent;
215             };
216 1886 50       4889 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   374 }
  57         135  
  57         300  
225              
226             sub UNIVERSAL::Private : ATTR(CODE) {
227 0     0 0 0 my ($package, $symbol, $referent, $attr, $data) = @_;
228 57     57   15154 no warnings 'redefine';
  57         122  
  57         8360  
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   355 }
  57         114  
  57         217  
242              
243             sub UNIVERSAL::Protected : ATTR(CODE) {
244 0     0 0 0 my ($package, $symbol, $referent, $attr, $data) = @_;
245 57     57   13342 no warnings 'redefine';
  57         120  
  57         10802  
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   357 }
  57         114  
  57         217  
263              
264             sub UNIVERSAL::Constructor : ATTR(CODE) {
265 180     180 0 368503 my ($package, $symbol, $referent, $attr, $data) = @_;
266 180         547 _handler(@_);
267 57     57   14841 }
  57         111  
  57         215  
268              
269             sub UNIVERSAL::Destructor : ATTR(CODE) {
270 272     272 0 107944 my ($package, $symbol, $referent, $attr, $data) = @_;
271 272         741 _handler(@_);
272 57     57   14839 }
  57         115  
  57         199  
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   15176 }
  57         115  
  57         191  
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   15967 }
  57         119  
  57         219  
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   15146 no warnings 'redefine';
  57         118  
  57         3694  
289 0     0   0 *$symbol = sub { throw 'NotImplemented' => "Abstract method, can't call $symbol" };
  0         0  
290 57     57   311 }
  57         109  
  57         206  
291              
292             sub UNIVERSAL::Clonable : ATTR(CODE) {
293 1316     1316 0 492451 my ($package, $symbol, $referent, $attr, $data) = @_;
294 1316         3151 _handler(@_);
295 57     57   15853 }
  57         105  
  57         229  
296              
297             sub UNIVERSAL::DeepClonable : ATTR(CODE) {
298 118     118 0 5847 my ($package, $symbol, $referent, $attr, $data) = @_;
299 118         305 _handler(@_);
300 57     57   15918 }
  57         143  
  57         191  
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   16858 }
  57         173  
  57         190  
306              
307             1;
308              
309