Commit | Line | Data |
a05f85c1 |
1 | |
2 | package Moose::Meta::Method::Delegation; |
3 | |
4 | use strict; |
5 | use warnings; |
6 | |
7 | use Carp 'confess'; |
8 | use Scalar::Util 'blessed', 'weaken'; |
9 | |
870d0f1a |
10 | our $VERSION = '0.94'; |
a05f85c1 |
11 | $VERSION = eval $VERSION; |
12 | our $AUTHORITY = 'cpan:STEVAN'; |
13 | |
01164604 |
14 | use base 'Moose::Meta::Method', |
8aeb5bfd |
15 | 'Class::MOP::Method::Inlined'; |
a05f85c1 |
16 | |
17 | |
18 | sub new { |
19 | my $class = shift; |
20 | my %options = @_; |
21 | |
46f7e6a5 |
22 | ( exists $options{attribute} ) |
a05f85c1 |
23 | || confess "You must supply an attribute to construct with"; |
24 | |
46f7e6a5 |
25 | ( blessed( $options{attribute} ) |
26 | && $options{attribute}->isa('Moose::Meta::Attribute') ) |
27 | || confess |
28 | "You must supply an attribute which is a 'Moose::Meta::Attribute' instance"; |
a05f85c1 |
29 | |
46f7e6a5 |
30 | ( $options{package_name} && $options{name} ) |
31 | || confess |
32 | "You must supply the package_name and name parameters $Class::MOP::Method::UPGRADE_ERROR_TEXT"; |
a05f85c1 |
33 | |
46f7e6a5 |
34 | ( $options{delegate_to_method} && ( !ref $options{delegate_to_method} ) |
35 | || ( 'CODE' eq ref $options{delegate_to_method} ) ) |
36 | || confess |
37 | 'You must supply a delegate_to_method which is a method name or a CODE reference'; |
a05f85c1 |
38 | |
4adb0e7b |
39 | exists $options{curried_arguments} |
40 | || ( $options{curried_arguments} = [] ); |
2de18801 |
41 | |
4adb0e7b |
42 | ( $options{curried_arguments} && |
43 | ( 'ARRAY' eq ref $options{curried_arguments} ) ) |
44 | || confess 'You must supply a curried_arguments which is an ARRAY reference'; |
2de18801 |
45 | |
46f7e6a5 |
46 | my $self = $class->_new( \%options ); |
47 | |
48 | weaken( $self->{'attribute'} ); |
49 | |
50 | $self->_initialize_body; |
a05f85c1 |
51 | |
52 | return $self; |
53 | } |
54 | |
01cd78f8 |
55 | sub _new { |
56 | my $class = shift; |
57 | my $options = @_ == 1 ? $_[0] : {@_}; |
58 | |
59 | return bless $options, $class; |
60 | } |
61 | |
2de18801 |
62 | sub curried_arguments { (shift)->{'curried_arguments'} } |
63 | |
01cd78f8 |
64 | sub associated_attribute { (shift)->{'attribute'} } |
65 | |
46f7e6a5 |
66 | sub delegate_to_method { (shift)->{'delegate_to_method'} } |
67 | |
8aeb5bfd |
68 | sub is_inline { |
69 | (shift)->{is_inline} |
70 | } |
71 | |
72 | sub definition_context { |
73 | exists $_[0]->{definition_context} ? $_[0]->{definition_context} |
74 | : ($_[0]->{definition_context} = $_[0]->_generate_definition_context); |
75 | } |
76 | |
77 | sub _generate_definition_context { |
78 | my $self = shift; |
79 | my $attr = $self->associated_attribute; |
80 | my $ctx = $attr->definition_context; |
81 | return unless $ctx; |
82 | |
83 | my $desc = "delegation of " |
84 | . $self->name |
85 | . ' to ' |
86 | . $attr->name |
87 | . '->' |
88 | . $self->delegate_to_method; |
89 | |
90 | return { %$ctx, description => $desc }; |
91 | } |
92 | |
46f7e6a5 |
93 | sub _initialize_body { |
94 | my $self = shift; |
95 | |
96 | my $method_to_call = $self->delegate_to_method; |
97 | return $self->{body} = $method_to_call |
98 | if ref $method_to_call; |
99 | |
8aeb5bfd |
100 | return $self->{body} = $self->is_inline |
101 | ? $self->_generate_body_inline |
102 | : $self->_generate_body; |
103 | } |
104 | |
105 | sub _generate_body_inline { |
106 | my $self = shift; |
107 | my $method_to_call = $self->delegate_to_method; |
108 | my $attr = $self->associated_attribute; |
109 | my $attr_name = $attr->name; |
110 | my $meta_instance = $attr->associated_class->instance_metaclass; |
111 | my $handle_name = $self->name; |
112 | |
113 | my ( $code, $e ) = $self->_compile_code( |
114 | environment => { |
115 | '@curried_arguments' => $self->curried_arguments, |
116 | '$method' => \$self, |
117 | }, |
118 | code => ( |
119 | 'sub {'."\n" |
120 | . 'my $instance = shift; '."\n" |
121 | . 'my $proxy = ' |
122 | . $meta_instance->inline_get_slot_value('$instance',$attr_name) |
123 | . ';'."\n" |
124 | . 'my $error '."\n" |
125 | . ' = !defined $proxy ? q{ is not defined} '."\n" |
126 | . q{ : ref($proxy) && !Scalar::Util::blessed($proxy) ? qq{ is not an object (got '$proxy')} }."\n" |
127 | . ' : undef;'."\n" |
128 | . 'if ($error) {'."\n" |
129 | . ' $method->throw_error('."\n" |
130 | . ' "Cannot delegate '. $handle_name.' to '.$method_to_call |
131 | . ' because the value of ' |
132 | . $attr_name |
133 | . '" . $error, '."\n" |
134 | . ' method_name => q{'.$method_to_call.'}, '."\n" |
135 | . ' object => $instance, '."\n" |
136 | . ');}'."\n" |
137 | . ($self->curried_arguments |
138 | ? 'unshift @_, @{curried_arguments};'."\n" |
139 | : '') |
140 | . '$proxy->'.$method_to_call.'(@_);'."\n" |
141 | . '};' |
142 | ), |
143 | ); |
144 | confess "Could not generate inline accessor because : $e" if $e; |
145 | |
146 | return $code; |
147 | } |
148 | |
149 | sub _generate_body { |
150 | my $self = shift; |
151 | |
152 | my $method_to_call = $self->delegate_to_method; |
153 | |
46f7e6a5 |
154 | my $accessor = $self->_get_delegate_accessor; |
155 | |
156 | my $handle_name = $self->name; |
157 | |
158 | # NOTE: we used to do a goto here, but the goto didn't handle |
159 | # failure correctly (it just returned nothing), so I took that |
160 | # out. However, the more I thought about it, the less I liked it |
16f00af3 |
161 | # doing the goto, and I preferred the act of delegation being |
46f7e6a5 |
162 | # actually represented in the stack trace. - SL |
5120f8ff |
163 | # not inlining this, since it won't really speed things up at |
164 | # all... the only thing that would end up different would be |
165 | # interpolating in $method_to_call, and a bunch of things in the |
166 | # error handling that mostly never gets called - doy |
8aeb5bfd |
167 | return sub { |
46f7e6a5 |
168 | my $instance = shift; |
169 | my $proxy = $instance->$accessor(); |
6148c167 |
170 | |
171 | my $error |
2cc6f982 |
172 | = !defined $proxy ? ' is not defined' |
173 | : ref($proxy) && !blessed($proxy) ? qq{ is not an object (got '$proxy')} |
174 | : undef; |
6148c167 |
175 | |
176 | if ($error) { |
177 | $self->throw_error( |
178 | "Cannot delegate $handle_name to $method_to_call because " |
179 | . "the value of " |
180 | . $self->associated_attribute->name |
181 | . $error, |
182 | method_name => $method_to_call, |
183 | object => $instance |
46f7e6a5 |
184 | ); |
6148c167 |
185 | } |
c279b82f |
186 | unshift @_, @{ $self->curried_arguments }; |
187 | $proxy->$method_to_call(@_); |
46f7e6a5 |
188 | }; |
189 | } |
190 | |
191 | sub _get_delegate_accessor { |
192 | my $self = shift; |
53b3e214 |
193 | my $attr = $self->associated_attribute; |
194 | |
195 | # NOTE: |
196 | # always use a named method when |
197 | # possible, if you use the method |
198 | # ref and there are modifiers on |
199 | # the accessors then it will not |
200 | # pick up the modifiers too. Only |
201 | # the named method will assure that |
202 | # we also have any modifiers run. |
203 | # - SL |
204 | my $accessor = $attr->has_read_method |
205 | ? $attr->get_read_method |
206 | : $attr->get_read_method_ref; |
207 | |
208 | $accessor = $accessor->body if Scalar::Util::blessed $accessor; |
46f7e6a5 |
209 | |
210 | return $accessor; |
211 | } |
212 | |
a05f85c1 |
213 | 1; |
01cd78f8 |
214 | |
215 | __END__ |
216 | |
217 | =pod |
218 | |
219 | =head1 NAME |
220 | |
221 | Moose::Meta::Method::Delegation - A Moose Method metaclass for delegation methods |
222 | |
223 | =head1 DESCRIPTION |
224 | |
225 | This is a subclass of L<Moose::Meta::Method> for delegation |
226 | methods. |
227 | |
228 | =head1 METHODS |
229 | |
230 | =over 4 |
231 | |
e6fb6ad2 |
232 | =item B<< Moose::Meta::Method::Delegation->new(%options) >> |
01cd78f8 |
233 | |
e6fb6ad2 |
234 | This creates the delegation methods based on the provided C<%options>. |
01cd78f8 |
235 | |
236 | =over 4 |
237 | |
238 | =item I<attribute> |
239 | |
240 | This must be an instance of C<Moose::Meta::Attribute> which this |
e6fb6ad2 |
241 | accessor is being generated for. This options is B<required>. |
01cd78f8 |
242 | |
46f7e6a5 |
243 | =item I<delegate_to_method> |
244 | |
245 | The method in the associated attribute's value to which we |
246 | delegate. This can be either a method name or a code reference. |
247 | |
2de18801 |
248 | =item I<curried_arguments> |
249 | |
250 | An array reference of arguments that will be prepended to the argument list for |
251 | any call to the delegating method. |
252 | |
01cd78f8 |
253 | =back |
254 | |
e6fb6ad2 |
255 | =item B<< $metamethod->associated_attribute >> |
01cd78f8 |
256 | |
257 | Returns the attribute associated with this method. |
258 | |
2de18801 |
259 | =item B<< $metamethod->curried_arguments >> |
260 | |
261 | Return any curried arguments that will be passed to the delegated method. |
262 | |
e6fb6ad2 |
263 | =item B<< $metamethod->delegate_to_method >> |
46f7e6a5 |
264 | |
e6fb6ad2 |
265 | Returns the method to which this method delegates, as passed to the |
266 | constructor. |
46f7e6a5 |
267 | |
01cd78f8 |
268 | =back |
269 | |
270 | =head1 BUGS |
271 | |
d4048ef3 |
272 | See L<Moose/BUGS> for details on reporting bugs. |
01cd78f8 |
273 | |
274 | =head1 AUTHOR |
275 | |
276 | Dave Rolsky E<lt>autarch@urth.orgE<gt> |
277 | |
278 | =head1 COPYRIGHT AND LICENSE |
279 | |
2840a3b2 |
280 | Copyright 2009 by Infinity Interactive, Inc. |
01cd78f8 |
281 | |
282 | L<http://www.iinteractive.com> |
283 | |
284 | This library is free software; you can redistribute it and/or modify |
285 | it under the same terms as Perl itself. |
286 | |
287 | =cut |