Tweaks for method modifiers
[gitmo/Mouse.git] / lib / Mouse / Meta / Role.pm
CommitLineData
a2227e71 1package Mouse::Meta::Role;
5af36247 2use Mouse::Util qw(:meta not_supported); # enables strict and warnings
74be9f76 3
6d28c5cf 4use Mouse::Meta::Module;
f3bb863f 5our @ISA = qw(Mouse::Meta::Module);
a2227e71 6
e058b279 7sub method_metaclass;
6cfa1e5e 8
8e64d0fa 9sub _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
27sub create_anon_role{
28 my $self = shift;
29 return $self->create(undef, @_);
30}
31
43165725 32sub is_anon_role;
a2227e71 33
43165725 34sub get_roles;
afc73948 35
e7264861 36sub 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 43sub get_required_method_list{
44 return @{ $_[0]->{required_methods} };
45}
afc73948 46
59089ec3 47sub 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 54sub requires_method {
55 my($self, $name) = @_;
56 return scalar( grep{ $_ eq $name } @{ $self->{required_methods} } ) != 0;
57}
58
274b6cce 59sub 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 67sub _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 96sub _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
131sub _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
143sub _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 170sub _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
185sub 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 248sub 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 262sub add_before_method_modifier;
263sub add_around_method_modifier;
264sub add_after_method_modifier;
3a63a2e7 265
cb60d0b5 266sub get_before_method_modifiers;
267sub get_around_method_modifiers;
268sub get_after_method_modifiers;
47f36c05 269
6cfa1e5e 270sub 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 284sub get_override_method_modifier {
285 my ($self, $method_name) = @_;
286 return $self->{override_method_modifiers}->{$method_name};
6cfa1e5e 287}
288
67199842 289sub 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 3061;
1820fffe 307__END__
308
309=head1 NAME
310
311Mouse::Meta::Role - The Mouse Role metaclass
312
a25ca8d6 313=head1 VERSION
314
12f4a95a 315This document describes Mouse version 0.67
a25ca8d6 316
1820fffe 317=head1 SEE ALSO
318
319L<Moose::Meta::Role>
320
321=cut