Commit | Line | Data |
a2227e71 |
1 | package Mouse::Meta::Role; |
5af36247 |
2 | use Mouse::Util qw(:meta not_supported); # enables strict and warnings |
74be9f76 |
3 | |
6d28c5cf |
4 | use Mouse::Meta::Module; |
f3bb863f |
5 | our @ISA = qw(Mouse::Meta::Module); |
a2227e71 |
6 | |
e058b279 |
7 | sub method_metaclass; |
6cfa1e5e |
8 | |
8e64d0fa |
9 | sub _construct_meta { |
acf0f643 |
10 | my $class = shift; |
7a50b450 |
11 | |
acf0f643 |
12 | my %args = @_; |
13 | |
5132ec42 |
14 | $args{methods} = {}; |
15 | $args{attributes} = {}; |
16 | $args{required_methods} = []; |
17 | $args{roles} = []; |
274b6cce |
18 | |
9009aca1 |
19 | my $self = bless \%args, ref($class) || $class; |
20 | if($class ne __PACKAGE__){ |
21 | $self->meta->_initialize_object($self, \%args); |
22 | } |
7a50b450 |
23 | |
9009aca1 |
24 | return $self; |
7a50b450 |
25 | } |
26 | |
27 | sub create_anon_role{ |
28 | my $self = shift; |
29 | return $self->create(undef, @_); |
30 | } |
31 | |
43165725 |
32 | sub is_anon_role; |
a2227e71 |
33 | |
43165725 |
34 | sub get_roles; |
afc73948 |
35 | |
e7264861 |
36 | sub calculate_all_roles { |
37 | my $self = shift; |
38 | my %seen; |
39 | return grep { !$seen{ $_->name }++ } |
40 | ($self, map { $_->calculate_all_roles } @{ $self->get_roles }); |
41 | } |
42 | |
6cfa1e5e |
43 | sub get_required_method_list{ |
44 | return @{ $_[0]->{required_methods} }; |
45 | } |
afc73948 |
46 | |
59089ec3 |
47 | sub add_required_methods { |
ea249879 |
48 | my($self, @methods) = @_; |
71e7b544 |
49 | my %required = map{ $_ => 1 } @{$self->{required_methods}}; |
50 | push @{$self->{required_methods}}, grep{ !$required{$_}++ && !$self->has_method($_) } @methods; |
51 | return; |
59089ec3 |
52 | } |
53 | |
6cfa1e5e |
54 | sub requires_method { |
55 | my($self, $name) = @_; |
56 | return scalar( grep{ $_ eq $name } @{ $self->{required_methods} } ) != 0; |
57 | } |
58 | |
274b6cce |
59 | sub add_attribute { |
60 | my $self = shift; |
61 | my $name = shift; |
6cfa1e5e |
62 | |
63 | $self->{attributes}->{$name} = (@_ == 1) ? $_[0] : { @_ }; |
c9313657 |
64 | return; |
da0c885d |
65 | } |
66 | |
3a63a2e7 |
67 | sub _check_required_methods{ |
45f22b92 |
68 | my($role, $consumer, $args) = @_; |
3a63a2e7 |
69 | |
71e7b544 |
70 | if($args->{_to} eq 'role'){ |
45f22b92 |
71 | $consumer->add_required_methods($role->get_required_method_list); |
71e7b544 |
72 | } |
73 | else{ # to class or instance |
45f22b92 |
74 | my $consumer_class_name = $consumer->name; |
2d2e77f9 |
75 | |
7a50b450 |
76 | my @missing; |
3a63a2e7 |
77 | foreach my $method_name(@{$role->{required_methods}}){ |
2d2e77f9 |
78 | next if exists $args->{aliased_methods}{$method_name}; |
79 | next if exists $role->{methods}{$method_name}; |
45f22b92 |
80 | next if $consumer_class_name->can($method_name); |
2d2e77f9 |
81 | |
82 | push @missing, $method_name; |
3a63a2e7 |
83 | } |
7a50b450 |
84 | if(@missing){ |
2d2e77f9 |
85 | $role->throw_error(sprintf "'%s' requires the method%s %s to be implemented by '%s'", |
86 | $role->name, |
87 | (@missing == 1 ? '' : 's'), # method or methods |
5af36247 |
88 | Mouse::Util::quoted_english_list(@missing), |
45f22b92 |
89 | $consumer_class_name); |
7a50b450 |
90 | } |
91 | } |
2e92bb89 |
92 | |
3a63a2e7 |
93 | return; |
2e92bb89 |
94 | } |
95 | |
3a63a2e7 |
96 | sub _apply_methods{ |
45f22b92 |
97 | my($role, $consumer, $args) = @_; |
da0c885d |
98 | |
7a50b450 |
99 | my $alias = $args->{-alias}; |
100 | my $excludes = $args->{-excludes}; |
e0b163e1 |
101 | |
3a63a2e7 |
102 | foreach my $method_name($role->get_method_list){ |
103 | next if $method_name eq 'meta'; |
104 | |
71e7b544 |
105 | my $code = $role->get_method_body($method_name); |
6cfa1e5e |
106 | |
2d2e77f9 |
107 | if(!exists $excludes->{$method_name}){ |
45f22b92 |
108 | if(!$consumer->has_method($method_name)){ |
2d2e77f9 |
109 | # The third argument $role is used in Role::Composite |
45f22b92 |
110 | $consumer->add_method($method_name => $code, $role); |
6cfa1e5e |
111 | } |
2e92bb89 |
112 | } |
2e92bb89 |
113 | |
2d2e77f9 |
114 | if(exists $alias->{$method_name}){ |
3a63a2e7 |
115 | my $dstname = $alias->{$method_name}; |
6cfa1e5e |
116 | |
45f22b92 |
117 | my $dstcode = $consumer->get_method_body($dstname); |
7a50b450 |
118 | |
119 | if(defined($dstcode) && $dstcode != $code){ |
71e7b544 |
120 | $role->throw_error("Cannot create a method alias if a local method of the same name exists"); |
21498b08 |
121 | } |
3a63a2e7 |
122 | else{ |
45f22b92 |
123 | $consumer->add_method($dstname => $code, $role); |
2e92bb89 |
124 | } |
59089ec3 |
125 | } |
126 | } |
127 | |
3a63a2e7 |
128 | return; |
129 | } |
130 | |
131 | sub _apply_attributes{ |
230dd14a |
132 | #my($role, $consumer, $args) = @_; |
133 | my($role, $consumer) = @_; |
3a63a2e7 |
134 | |
71e7b544 |
135 | for my $attr_name ($role->get_attribute_list) { |
45f22b92 |
136 | next if $consumer->has_attribute($attr_name); |
3a63a2e7 |
137 | |
45f22b92 |
138 | $consumer->add_attribute($attr_name => $role->get_attribute($attr_name)); |
da0c885d |
139 | } |
3a63a2e7 |
140 | return; |
141 | } |
142 | |
143 | sub _apply_modifiers{ |
230dd14a |
144 | #my($role, $consumer, $args) = @_; |
145 | my($role, $consumer) = @_; |
146 | |
3a63a2e7 |
147 | |
2d2e77f9 |
148 | if(my $modifiers = $role->{override_method_modifiers}){ |
149 | foreach my $method_name (keys %{$modifiers}){ |
45f22b92 |
150 | $consumer->add_override_method_modifier($method_name => $modifiers->{$method_name}); |
2d2e77f9 |
151 | } |
152 | } |
153 | |
154 | for my $modifier_type (qw/before around after/) { |
467c4815 |
155 | my $table = $role->{"${modifier_type}_method_modifiers"} |
71e7b544 |
156 | or next; |
157 | |
3a63a2e7 |
158 | my $add_modifier = "add_${modifier_type}_method_modifier"; |
d99db7b6 |
159 | |
467c4815 |
160 | while(my($method_name, $modifiers) = each %{$table}){ |
161 | foreach my $code(@{ $modifiers }){ |
45f22b92 |
162 | next if $consumer->{"_applied_$modifier_type"}{$method_name, $code}++; # skip applied modifiers |
163 | $consumer->$add_modifier($method_name => $code); |
d99db7b6 |
164 | } |
165 | } |
166 | } |
3a63a2e7 |
167 | return; |
da0c885d |
168 | } |
0fc8adbc |
169 | |
3a63a2e7 |
170 | sub _append_roles{ |
230dd14a |
171 | #my($role, $consumer, $args) = @_; |
172 | my($role, $consumer) = @_; |
21498b08 |
173 | |
467c4815 |
174 | my $roles = $consumer->{roles}; |
3a63a2e7 |
175 | |
176 | foreach my $r($role, @{$role->get_roles}){ |
f3e11122 |
177 | if(!$consumer->does_role($r)){ |
3a63a2e7 |
178 | push @{$roles}, $r; |
21498b08 |
179 | } |
180 | } |
3a63a2e7 |
181 | return; |
182 | } |
21498b08 |
183 | |
3a63a2e7 |
184 | # Moose uses Application::ToInstance, Application::ToClass, Application::ToRole |
185 | sub apply { |
45f22b92 |
186 | my $self = shift; |
187 | my $consumer = shift; |
7a50b450 |
188 | |
71e7b544 |
189 | my %args = (@_ == 1) ? %{ $_[0] } : @_; |
60b5c3be |
190 | |
f774b7de |
191 | my $instance; |
192 | |
45f22b92 |
193 | if(Mouse::Util::is_a_metaclass($consumer)){ # Application::ToClass |
71e7b544 |
194 | $args{_to} = 'class'; |
195 | } |
45f22b92 |
196 | elsif(Mouse::Util::is_a_metarole($consumer)){ # Application::ToRole |
71e7b544 |
197 | $args{_to} = 'role'; |
198 | } |
199 | else{ # Appplication::ToInstance |
200 | $args{_to} = 'instance'; |
45f22b92 |
201 | $instance = $consumer; |
60b5c3be |
202 | |
45f22b92 |
203 | $consumer = (Mouse::Util::class_of($instance) || 'Mouse::Meta::Class')->create_anon_class( |
f774b7de |
204 | superclasses => [ref $instance], |
71e7b544 |
205 | cache => 1, |
206 | ); |
71e7b544 |
207 | } |
60b5c3be |
208 | |
71e7b544 |
209 | if($args{alias} && !exists $args{-alias}){ |
210 | $args{-alias} = $args{alias}; |
211 | } |
212 | if($args{excludes} && !exists $args{-excludes}){ |
213 | $args{-excludes} = $args{excludes}; |
214 | } |
60b5c3be |
215 | |
2d2e77f9 |
216 | $args{aliased_methods} = {}; |
71e7b544 |
217 | if(my $alias = $args{-alias}){ |
2d2e77f9 |
218 | @{$args{aliased_methods}}{ values %{$alias} } = (); |
60b5c3be |
219 | } |
71e7b544 |
220 | |
221 | if(my $excludes = $args{-excludes}){ |
222 | $args{-excludes} = {}; # replace with a hash ref |
223 | if(ref $excludes){ |
224 | %{$args{-excludes}} = (map{ $_ => undef } @{$excludes}); |
60b5c3be |
225 | } |
226 | else{ |
71e7b544 |
227 | $args{-excludes}{$excludes} = undef; |
60b5c3be |
228 | } |
229 | } |
230 | |
45f22b92 |
231 | $self->_check_required_methods($consumer, \%args); |
232 | $self->_apply_attributes($consumer, \%args); |
233 | $self->_apply_methods($consumer, \%args); |
234 | $self->_apply_modifiers($consumer, \%args); |
235 | $self->_append_roles($consumer, \%args); |
f774b7de |
236 | |
237 | |
238 | if(defined $instance){ # Application::ToInstance |
239 | # rebless instance |
45f22b92 |
240 | bless $instance, $consumer->name; |
6c7491f2 |
241 | $consumer->_initialize_object($instance, $instance, 1); |
f774b7de |
242 | } |
243 | |
71e7b544 |
244 | return; |
245 | } |
246 | |
21498b08 |
247 | |
71e7b544 |
248 | sub combine { |
230dd14a |
249 | my($self, @role_specs) = @_; |
21498b08 |
250 | |
71e7b544 |
251 | require 'Mouse/Meta/Role/Composite.pm'; # we don't want to create its namespace |
7a50b450 |
252 | |
71e7b544 |
253 | my $composite = Mouse::Meta::Role::Composite->create_anon_role(); |
254 | |
255 | foreach my $role_spec (@role_specs) { |
256 | my($role_name, $args) = @{$role_spec}; |
257 | $role_name->meta->apply($composite, %{$args}); |
21498b08 |
258 | } |
71e7b544 |
259 | return $composite; |
21498b08 |
260 | } |
261 | |
cb60d0b5 |
262 | sub add_before_method_modifier; |
263 | sub add_around_method_modifier; |
264 | sub add_after_method_modifier; |
3a63a2e7 |
265 | |
cb60d0b5 |
266 | sub get_before_method_modifiers; |
267 | sub get_around_method_modifiers; |
268 | sub get_after_method_modifiers; |
47f36c05 |
269 | |
6cfa1e5e |
270 | sub add_override_method_modifier{ |
271 | my($self, $method_name, $method) = @_; |
272 | |
60b5c3be |
273 | if($self->has_method($method_name)){ |
274 | # This error happens in the override keyword or during role composition, |
275 | # so I added a message, "A local method of ...", only for compatibility (gfx) |
8e64d0fa |
276 | $self->throw_error("Cannot add an override of method '$method_name' " |
60b5c3be |
277 | . "because there is a local version of '$method_name'" |
278 | . "(A local method of the same name as been found)"); |
279 | } |
6cfa1e5e |
280 | |
281 | $self->{override_method_modifiers}->{$method_name} = $method; |
282 | } |
283 | |
8e64d0fa |
284 | sub get_override_method_modifier { |
285 | my ($self, $method_name) = @_; |
286 | return $self->{override_method_modifiers}->{$method_name}; |
6cfa1e5e |
287 | } |
288 | |
67199842 |
289 | sub does_role { |
290 | my ($self, $role_name) = @_; |
291 | |
292 | (defined $role_name) |
fce211ae |
293 | || $self->throw_error("You must supply a role name to look for"); |
67199842 |
294 | |
f3e11122 |
295 | $role_name = $role_name->name if ref $role_name; |
296 | |
67199842 |
297 | # if we are it,.. then return true |
298 | return 1 if $role_name eq $self->name; |
3a63a2e7 |
299 | # otherwise.. check our children |
300 | for my $role (@{ $self->get_roles }) { |
67199842 |
301 | return 1 if $role->does_role($role_name); |
302 | } |
303 | return 0; |
304 | } |
305 | |
a2227e71 |
306 | 1; |
1820fffe |
307 | __END__ |
308 | |
309 | =head1 NAME |
310 | |
311 | Mouse::Meta::Role - The Mouse Role metaclass |
312 | |
a25ca8d6 |
313 | =head1 VERSION |
314 | |
86eb0b5e |
315 | This document describes Mouse version 0.70 |
a25ca8d6 |
316 | |
503ed648 |
317 | =head1 DESCRIPTION |
318 | |
319 | This class is a meta object protocol for Mouse roles, |
320 | which is a subset of Moose::Meta:::Role. |
321 | |
1820fffe |
322 | =head1 SEE ALSO |
323 | |
324 | L<Moose::Meta::Role> |
325 | |
326 | =cut |