built-in type constraints in XS
[gitmo/Mouse.git] / lib / Mouse / Meta / Role.pm
CommitLineData
a2227e71 1package Mouse::Meta::Role;
bc69ee88 2use Mouse::Util qw(:meta not_supported english_list); # enables strict and warnings
74be9f76 3
6d28c5cf 4use Mouse::Meta::Module;
f3bb863f 5our @ISA = qw(Mouse::Meta::Module);
a2227e71 6
6cfa1e5e 7sub method_metaclass(){ 'Mouse::Meta::Role::Method' } # required for get_method()
8
8e64d0fa 9sub _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
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{
71e7b544 68 my($role, $applicant, $args) = @_;
3a63a2e7 69
71e7b544 70 if($args->{_to} eq 'role'){
71 $applicant->add_required_methods($role->get_required_method_list);
72 }
73 else{ # to class or instance
2d2e77f9 74 my $applicant_class_name = $applicant->name;
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};
80 next if $applicant_class_name->can($method_name);
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
88 english_list(map{ sprintf q{'%s'}, $_ } @missing),
89 $applicant_class_name);
7a50b450 90 }
91 }
2e92bb89 92
3a63a2e7 93 return;
2e92bb89 94}
95
3a63a2e7 96sub _apply_methods{
71e7b544 97 my($role, $applicant, $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}){
71e7b544 108 if(!$applicant->has_method($method_name)){
2d2e77f9 109 # The third argument $role is used in Role::Composite
71e7b544 110 $applicant->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
71e7b544 117 my $dstcode = $applicant->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{
71e7b544 123 $applicant->add_method($dstname => $code, $role);
2e92bb89 124 }
59089ec3 125 }
126 }
127
3a63a2e7 128 return;
129}
130
131sub _apply_attributes{
71e7b544 132 my($role, $applicant, $args) = @_;
3a63a2e7 133
71e7b544 134 for my $attr_name ($role->get_attribute_list) {
135 next if $applicant->has_attribute($attr_name);
3a63a2e7 136
71e7b544 137 $applicant->add_attribute($attr_name => $role->get_attribute($attr_name));
da0c885d 138 }
3a63a2e7 139 return;
140}
141
142sub _apply_modifiers{
71e7b544 143 my($role, $applicant, $args) = @_;
3a63a2e7 144
2d2e77f9 145 if(my $modifiers = $role->{override_method_modifiers}){
146 foreach my $method_name (keys %{$modifiers}){
147 $applicant->add_override_method_modifier($method_name => $modifiers->{$method_name});
148 }
149 }
150
151 for my $modifier_type (qw/before around after/) {
71e7b544 152 my $modifiers = $role->{"${modifier_type}_method_modifiers"}
153 or next;
154
3a63a2e7 155 my $add_modifier = "add_${modifier_type}_method_modifier";
d99db7b6 156
71e7b544 157 foreach my $method_name (keys %{$modifiers}){
2d2e77f9 158 foreach my $code(@{ $modifiers->{$method_name} }){
159 next if $applicant->{"_applied_$modifier_type"}{$method_name, $code}++; # skip applied modifiers
71e7b544 160 $applicant->$add_modifier($method_name => $code);
d99db7b6 161 }
162 }
163 }
3a63a2e7 164 return;
da0c885d 165}
0fc8adbc 166
3a63a2e7 167sub _append_roles{
71e7b544 168 my($role, $applicant, $args) = @_;
21498b08 169
71e7b544 170 my $roles = ($args->{_to} eq 'role') ? $applicant->get_roles : $applicant->roles;
3a63a2e7 171
172 foreach my $r($role, @{$role->get_roles}){
71e7b544 173 if(!$applicant->does_role($r->name)){
3a63a2e7 174 push @{$roles}, $r;
21498b08 175 }
176 }
3a63a2e7 177 return;
178}
21498b08 179
3a63a2e7 180# Moose uses Application::ToInstance, Application::ToClass, Application::ToRole
181sub apply {
7a50b450 182 my $self = shift;
183 my $applicant = shift;
7a50b450 184
71e7b544 185 my %args = (@_ == 1) ? %{ $_[0] } : @_;
60b5c3be 186
f774b7de 187 my $instance;
188
71e7b544 189 if($applicant->isa('Mouse::Meta::Class')){ # Application::ToClass
190 $args{_to} = 'class';
191 }
192 elsif($applicant->isa('Mouse::Meta::Role')){ # Application::ToRole
193 $args{_to} = 'role';
194 }
195 else{ # Appplication::ToInstance
196 $args{_to} = 'instance';
f774b7de 197 $instance = $applicant;
60b5c3be 198
f774b7de 199 $applicant = (Mouse::Util::class_of($instance) || 'Mouse::Meta::Class')->create_anon_class(
200 superclasses => [ref $instance],
71e7b544 201 cache => 1,
202 );
71e7b544 203 }
60b5c3be 204
71e7b544 205 if($args{alias} && !exists $args{-alias}){
206 $args{-alias} = $args{alias};
207 }
208 if($args{excludes} && !exists $args{-excludes}){
209 $args{-excludes} = $args{excludes};
210 }
60b5c3be 211
2d2e77f9 212 $args{aliased_methods} = {};
71e7b544 213 if(my $alias = $args{-alias}){
2d2e77f9 214 @{$args{aliased_methods}}{ values %{$alias} } = ();
60b5c3be 215 }
71e7b544 216
217 if(my $excludes = $args{-excludes}){
218 $args{-excludes} = {}; # replace with a hash ref
219 if(ref $excludes){
220 %{$args{-excludes}} = (map{ $_ => undef } @{$excludes});
60b5c3be 221 }
222 else{
71e7b544 223 $args{-excludes}{$excludes} = undef;
60b5c3be 224 }
225 }
226
71e7b544 227 $self->_check_required_methods($applicant, \%args);
228 $self->_apply_attributes($applicant, \%args);
229 $self->_apply_methods($applicant, \%args);
230 $self->_apply_modifiers($applicant, \%args);
231 $self->_append_roles($applicant, \%args);
f774b7de 232
233
234 if(defined $instance){ # Application::ToInstance
235 # rebless instance
236 bless $instance, $applicant->name;
237 $applicant->_initialize_object($instance, $instance);
238 }
239
71e7b544 240 return;
241}
242
21498b08 243
71e7b544 244sub combine {
245 my($role_class, @role_specs) = @_;
21498b08 246
71e7b544 247 require 'Mouse/Meta/Role/Composite.pm'; # we don't want to create its namespace
7a50b450 248
71e7b544 249 my $composite = Mouse::Meta::Role::Composite->create_anon_role();
250
251 foreach my $role_spec (@role_specs) {
252 my($role_name, $args) = @{$role_spec};
253 $role_name->meta->apply($composite, %{$args});
21498b08 254 }
71e7b544 255 return $composite;
21498b08 256}
257
6cfa1e5e 258for my $modifier_type (qw/before after around/) {
3a63a2e7 259
260 my $modifier = "${modifier_type}_method_modifiers";
c9313657 261
3a63a2e7 262 my $add_method_modifier = sub {
fc0e0bbd 263 my ($self, $method_name, $method) = @_;
264
3a63a2e7 265 push @{ $self->{$modifier}->{$method_name} ||= [] }, $method;
266 return;
fc0e0bbd 267 };
c9313657 268
3a63a2e7 269 my $get_method_modifiers = sub {
270 my ($self, $method_name) = @_;
271 return @{ $self->{$modifier}->{$method_name} ||= [] }
c2f128e7 272 };
c2f128e7 273
3a63a2e7 274 no strict 'refs';
275 *{ 'add_' . $modifier_type . '_method_modifier' } = $add_method_modifier;
276 *{ 'get_' . $modifier_type . '_method_modifiers' } = $get_method_modifiers;
c9313657 277
278 # has_${modifier_type}_method_modifiers is moved into t::lib::Test::Mouse
3a63a2e7 279}
47f36c05 280
6cfa1e5e 281sub add_override_method_modifier{
282 my($self, $method_name, $method) = @_;
283
60b5c3be 284 if($self->has_method($method_name)){
285 # This error happens in the override keyword or during role composition,
286 # so I added a message, "A local method of ...", only for compatibility (gfx)
8e64d0fa 287 $self->throw_error("Cannot add an override of method '$method_name' "
60b5c3be 288 . "because there is a local version of '$method_name'"
289 . "(A local method of the same name as been found)");
290 }
6cfa1e5e 291
292 $self->{override_method_modifiers}->{$method_name} = $method;
293}
294
8e64d0fa 295sub get_override_method_modifier {
296 my ($self, $method_name) = @_;
297 return $self->{override_method_modifiers}->{$method_name};
6cfa1e5e 298}
299
67199842 300sub does_role {
301 my ($self, $role_name) = @_;
302
303 (defined $role_name)
fce211ae 304 || $self->throw_error("You must supply a role name to look for");
67199842 305
306 # if we are it,.. then return true
307 return 1 if $role_name eq $self->name;
3a63a2e7 308 # otherwise.. check our children
309 for my $role (@{ $self->get_roles }) {
67199842 310 return 1 if $role->does_role($role_name);
311 }
312 return 0;
313}
314
a2227e71 3151;
1820fffe 316__END__
317
318=head1 NAME
319
320Mouse::Meta::Role - The Mouse Role metaclass
321
a25ca8d6 322=head1 VERSION
323
034587d8 324This document describes Mouse version 0.40
a25ca8d6 325
1820fffe 326=head1 SEE ALSO
327
328L<Moose::Meta::Role>
329
330=cut