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