File Coverage

blib/lib/Method/Cached.pm
Criterion Covered Total %
statement 159 165 96.3
branch 60 76 78.9
condition 32 52 61.5
subroutine 29 29 100.0
pod 3 5 60.0
total 283 327 86.5


line stmt bran cond sub pod time code
1             package Method::Cached;
2              
3 6     6   2524 use strict;
  6         10  
  6         186  
4 6     6   27 use warnings;
  6         5  
  6         179  
5 6     6   3330 use Attribute::Handlers;
  6         29139  
  6         34  
6 6     6   1265 use B qw/svref_2object/;
  6         1186  
  6         445  
7 6     6   29 use Carp qw/croak confess/;
  6         7  
  6         289  
8 6     6   3133 use UNIVERSAL::require;
  6         6424  
  6         50  
9 6     6   2581 use Method::Cached::KeyRule;
  6         10  
  6         59  
10              
11             our $VERSION = '0.045_1';
12              
13             my %_DOMAINS;
14             my $_DEFAULT_DOMAIN = { storage_class => 'Cache::FastMmap' };
15             my %_METHOD_INFO;
16             my %_PREPARE_INFO;
17              
18             sub import {
19 17     17   389 my ($class, %args) = @_;
20 17 100       55 if ($class eq __PACKAGE__) {
21 13 100 66     77 if (exists $args{-domains} && defined $args{-domains}) {
22 2         3 my $domains = $args{-domains};
23 2 100       180 ref $domains eq 'HASH'
24             || confess '-domains option should be a hash reference';
25 1         2 $class->set_domain(%{ $domains });
  1         6  
26             }
27 12 100 66     53 if (exists $args{-default} && defined $args{-default}) {
28 3         5 my $default = $args{-default};
29 3 100       137 ref $default eq 'HASH'
30             || confess '-default option should be a hash reference';
31 2         3 $class->default_domain($default);
32             }
33             else {
34 9         25 _inspect_storage_class($_DEFAULT_DOMAIN->{storage_class});
35             }
36 9 50 33     54 unless (exists $args{-inherit} && $args{-inherit} eq 'no') {
37 9         24 my $caller = caller 0;
38 9 100 66     1755 if ($caller ne 'main' && ! $caller->isa(__PACKAGE__)) {
39 6     6   1890 no strict 'refs';
  6         8  
  6         1432  
40 4         8 unshift @{$caller . '::ISA'}, __PACKAGE__;
  4         307  
41             }
42             }
43             }
44             else {
45 4         34 $class->_apply_cached;
46             }
47             }
48              
49             sub UNIVERSAL::Cached :ATTR(CODE,BEGIN,INIT) {
50 48     48 0 10576 my ($package, $symbol, $code, $attr, $args, $phase, $file, $line) = @_;
51 48 100 33     139 $args = [ $args || () ] if ref $args ne 'ARRAY';
52 48         50 my $name;
53 48 100       118 if ($phase eq 'BEGIN') {
54 24   50     63 my $name = $package->_scan_symbol_name($file, $line) || return;
55 24         47 _prepare_info($package, $name, $code);
56 24         24 _method_info($package, $name, $code, _parse_attr_args(@{$args}));
  24         61  
57             }
58 48 100       129 if ($phase eq 'INIT') {
59 24         47 my $name = $package . '::' . *{$symbol}{NAME};
  24         70  
60 24         35 _method_info($package, $name, $code, _parse_attr_args(@{$args}));
  24         71  
61 24 50       111 _defined_code($name) || _replace_cached($name);
62             }
63 6     6   34 }
  6         7  
  6         53  
64              
65             sub delete {
66 1     1 0 14 my ($class, $name) = splice @_, 0, 2;
67 1 50       5 unless (exists $_METHOD_INFO{$name}) {
68 0 0       0 if ($name =~ /^(.*)::([^:]*)$/) {
69 0         0 my ($package, $method) = ($1, $2);
70 0 0       0 $package->require || confess "Can't load module: $package";
71             }
72             }
73 1 50       5 if (exists $_METHOD_INFO{$name}) {
74 1         4 my $info = $_METHOD_INFO{$name};
75 1         2 my $dname = $info->{domain};
76 1 50       4 my $domain = $_DOMAINS{$dname} ? $_DOMAINS{$dname} : $_DEFAULT_DOMAIN;
77 1   33     4 my $rule = $info->{key_rule} || $domain->{key_rule};
78 1         6 my $key = Method::Cached::KeyRule::regularize($rule, $info->{name}, [ @_ ]);
79 1         6 my $storage = _storage($domain);
80 1   33     23 my $dmethod = $storage->can('delete') || $storage->can('clear');
81 1         11 $dmethod->($storage, $key . $_) for qw/ :l :s /;
82             }
83             }
84              
85             sub default_domain {
86 4     4 1 29 my ($class, $args) = @_;
87 4 100       12 if ($args) {
88 3 50       8 exists $args->{key_rule} && delete $args->{key_rule};
89             $_DEFAULT_DOMAIN = {
90 3         5 %{ $_DEFAULT_DOMAIN },
91 3         13 %{ $args },
  3         8  
92             };
93 3         9 _inspect_storage_class($_DEFAULT_DOMAIN->{storage_class});
94             }
95 2         53 return $_DEFAULT_DOMAIN;
96             }
97              
98             sub set_domain {
99 1     1 1 3 my ($class, %args) = @_;
100 1         4 for my $name (keys %args) {
101 2         4 my $args = $args{$name};
102 2 50       5 if (exists $_DOMAINS{$name}) {
103 0         0 warn 'This domain has already been defined: ' . $name;
104 0         0 next;
105             }
106 2         4 $_DOMAINS{$name} = $args;
107 2         28 _inspect_storage_class($_DOMAINS{$name}->{storage_class});
108             }
109             }
110              
111             sub get_domain {
112 2     2 1 6 my ($class, $dname) = @_;
113 2         7 return $_DOMAINS{$dname};
114             }
115              
116             sub _scan_symbol_name {
117 24     24   29 my ($package, $file, $line) = @_;
118 6     6   4413 no strict 'refs';
  6         9  
  6         1132  
119 24         27 for (values %{$package . '::'}) {
  24         84  
120 266         1053 (my $symbol = $_) =~ s/^\*//;
121 266         230 my $gv = svref_2object(\*{$symbol});
  266         508  
122 266 50       405 next if ref $gv ne 'B::GV';
123 266 100 66     803 return $symbol if $line == $gv->LINE && $file eq $gv->FILE;
124             }
125 0         0 return;
126             }
127              
128             sub _apply_cached {
129 4     4   9 my $class = shift;
130 4 50       18 my $prof = exists $_PREPARE_INFO{$class} ? $_PREPARE_INFO{$class} : ();
131 4 50       15 return unless $prof;
132 4         9 for my $name (keys %{$prof}) {
  4         22  
133 24         37 _replace_cached($name);
134             }
135             }
136              
137             sub _replace_cached {
138 24     24   31 my $name = shift;
139 6     6   31 no strict 'refs';
  6         7  
  6         180  
140 6     6   23 no warnings;
  6         8  
  6         4087  
141 24     102   78 *{$name} = sub { unshift @_, $_METHOD_INFO{$name}, wantarray; goto &_wrapper };
  24         84  
  102         18010071  
  102         312  
142             }
143              
144             sub _parse_attr_args {
145 48     48   52 my $dname = q{};
146 48         53 my $expires = 0;
147 48         48 my $key_rule = undef;
148 48 100       97 if (0 < @_) {
149 46 100 66     295 if ((! defined $_[0]) || ($_[0] !~ /^?\d+$/)) {
150 24         32 $dname = shift;
151             }
152             }
153 48   100     142 $dname ||= q{};
154 48 100       90 if (0 < @_) {
155 46 50       198 $expires = ($_[0] =~ /^\d+$/) ? shift @_ : confess
156             'The first argument or the second argument should be a numeric value.';
157 46 100       119 $key_rule = shift if 0 < @_;
158             }
159 48         139 return ($dname, $expires, $key_rule);
160             }
161              
162             sub _prepare_info {
163 24     24   35 my ($package, $name, $code) = @_;
164 24   100     62 my $profile = $_PREPARE_INFO{$package} ||= {};
165 24         49 $profile->{$name} = $code;
166             }
167              
168             sub _defined_code {
169 24     24   31 my $name = shift;
170 24   50     62 my $info = $_METHOD_INFO{$name} || return;
171 24   50     69 my $prof = $_PREPARE_INFO{$info->{package}} || return;
172 24         150 $prof->{$name} eq $info->{code};
173             }
174              
175             sub _method_info {
176 48     48   74 my ($package, $name, $code, $dname, $expires, $key_rule) = @_;
177 48         263 $_METHOD_INFO{$name} = {
178             'package' => $package,
179             'name' => $name,
180             'code' => $code,
181             'domain' => $dname,
182             'expires' => $expires,
183             'key_rule' => $key_rule,
184             };
185             }
186              
187             sub _storage {
188 103     103   106 my $domain = shift;
189 103 100       376 $domain->{_storage_instance} && return $domain->{_storage_instance};
190 4   33     18 my $st_class = $domain->{storage_class} || croak 'storage_class is necessary';
191 4   100     23 my $st_args = $domain->{storage_args} || undef;
192 4 100       7 $domain->{_storage_instance} = $st_class->new(@{ $st_args || [] });
  4         53  
193             }
194              
195             sub _inspect_storage_class {
196 14     14   17 my $any_class = shift;
197 14         16 my $invalid;
198 14 100       76 $any_class->require || confess "Can't load module: $any_class";
199 13   100     28359 $any_class->can($_) || $invalid++ for qw/new set get/;
200 13 100 66     162 $any_class->can('delete') || $any_class->can('remove') || $invalid++;
201 13 100       422 $invalid && croak
202             'storage_class needs the following methods: new, set, get, delete or remove';
203             }
204              
205             sub _wrapper {
206 102     102   277 my ($info, $warray) = splice @_, 0, 2;
207 102         196 my $dname = $info->{domain};
208 102 50       690 my $domain = $_DOMAINS{$dname} ? $_DOMAINS{$dname} : $_DEFAULT_DOMAIN;
209 102   66     339 my $rule = $info->{key_rule} || $domain->{key_rule};
210 102         928 my $key = Method::Cached::KeyRule::regularize($rule, $info->{name}, [ @_ ]);
211 102 100       303 my $key_af = $key . ($warray ? ':l' : ':s');
212 102         192 my $storage = _storage($domain);
213 102         122890 my $ret = $storage->get($key_af);
214 102 100       240707 unless ($ret) {
215 71 100       364 $ret = [ $warray ? $info->{code}->(@_) : scalar $info->{code}->(@_) ];
216 71   100     1480 $storage->set($key_af, $ret, $info->{expires} || 0);
217             }
218 102 100       9217 return $warray ? @{ $ret } : $ret->[0];
  15         51  
219             }
220              
221             1;
222              
223             __END__