Changelogging
[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
32sub is_anon_role{
33 return exists $_[0]->{anon_serial_id};
acf0f643 34}
a2227e71 35
afc73948 36sub get_roles { $_[0]->{roles} }
37
6cfa1e5e 38sub get_required_method_list{
39 return @{ $_[0]->{required_methods} };
40}
afc73948 41
59089ec3 42sub 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 49sub requires_method {
50 my($self, $name) = @_;
51 return scalar( grep{ $_ eq $name } @{ $self->{required_methods} } ) != 0;
52}
53
274b6cce 54sub add_attribute {
55 my $self = shift;
56 my $name = shift;
6cfa1e5e 57
58 $self->{attributes}->{$name} = (@_ == 1) ? $_[0] : { @_ };
da0c885d 59}
60
3a63a2e7 61sub _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 88sub _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
126sub _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
137sub _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 157sub _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
171sub 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 228sub 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 242for 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 267sub 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 281sub has_override_method_modifier {
282 my ($self, $method_name) = @_;
283 return exists $self->{override_method_modifiers}->{$method_name};
284}
285
286sub get_override_method_modifier {
287 my ($self, $method_name) = @_;
288 return $self->{override_method_modifiers}->{$method_name};
6cfa1e5e 289}
290
291sub 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.
298sub 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 3141;
315
1820fffe 316__END__
317
318=head1 NAME
319
320Mouse::Meta::Role - The Mouse Role metaclass
321
322=head1 SEE ALSO
323
324L<Moose::Meta::Role>
325
326=cut