Runtime ROles
[gitmo/Moose.git] / lib / Moose / Meta / Role.pm
CommitLineData
e185c027 1
2package Moose::Meta::Role;
3
4use strict;
5use warnings;
6use metaclass;
7
bdabd620 8use Carp 'confess';
9use Scalar::Util 'blessed';
d30bc041 10use B 'svref_2object';
bdabd620 11
12use Moose::Meta::Class;
e185c027 13
e39d707f 14our $VERSION = '0.04';
e185c027 15
68efb014 16use base 'Class::MOP::Module';
80572233 17
68efb014 18## Attributes
80572233 19
20## roles
21
22__PACKAGE__->meta->add_attribute('roles' => (
23 reader => 'get_roles',
24 default => sub { [] }
e185c027 25));
26
d79e62fd 27## excluded roles
28
29__PACKAGE__->meta->add_attribute('excluded_roles_map' => (
30 reader => 'get_excluded_roles_map',
31 default => sub { {} }
32));
33
80572233 34## attributes
35
e185c027 36__PACKAGE__->meta->add_attribute('attribute_map' => (
37 reader => 'get_attribute_map',
38 default => sub { {} }
39));
40
1331430a 41## required methods
42
43__PACKAGE__->meta->add_attribute('required_methods' => (
44 reader => 'get_required_methods_map',
45 default => sub { {} }
46));
47
0558683c 48## method modifiers
49
50__PACKAGE__->meta->add_attribute('before_method_modifiers' => (
51 reader => 'get_before_method_modifiers_map',
52 default => sub { {} } # (<name> => [ (CODE) ])
53));
54
55__PACKAGE__->meta->add_attribute('after_method_modifiers' => (
56 reader => 'get_after_method_modifiers_map',
57 default => sub { {} } # (<name> => [ (CODE) ])
58));
59
60__PACKAGE__->meta->add_attribute('around_method_modifiers' => (
61 reader => 'get_around_method_modifiers_map',
62 default => sub { {} } # (<name> => [ (CODE) ])
63));
64
65__PACKAGE__->meta->add_attribute('override_method_modifiers' => (
66 reader => 'get_override_method_modifiers_map',
67 default => sub { {} } # (<name> => CODE)
68));
69
bdabd620 70## Methods
80572233 71
68efb014 72sub method_metaclass { 'Moose::Meta::Role::Method' }
e185c027 73
80572233 74## subroles
75
76sub add_role {
77 my ($self, $role) = @_;
78 (blessed($role) && $role->isa('Moose::Meta::Role'))
79 || confess "Roles must be instances of Moose::Meta::Role";
80 push @{$self->get_roles} => $role;
81}
82
b8aeb4dc 83sub calculate_all_roles {
84 my $self = shift;
85 my %seen;
86 grep { !$seen{$_->name}++ } $self, map { $_->calculate_all_roles } @{ $self->get_roles };
87}
88
80572233 89sub does_role {
90 my ($self, $role_name) = @_;
91 (defined $role_name)
92 || confess "You must supply a role name to look for";
bdabd620 93 # if we are it,.. then return true
94 return 1 if $role_name eq $self->name;
95 # otherwise.. check our children
80572233 96 foreach my $role (@{$self->get_roles}) {
bdabd620 97 return 1 if $role->does_role($role_name);
80572233 98 }
99 return 0;
100}
101
d79e62fd 102## excluded roles
103
104sub add_excluded_roles {
105 my ($self, @excluded_role_names) = @_;
106 $self->get_excluded_roles_map->{$_} = undef foreach @excluded_role_names;
107}
108
109sub get_excluded_roles_list {
110 my ($self) = @_;
111 keys %{$self->get_excluded_roles_map};
112}
113
114sub excludes_role {
115 my ($self, $role_name) = @_;
116 exists $self->get_excluded_roles_map->{$role_name} ? 1 : 0;
117}
118
1331430a 119## required methods
120
121sub add_required_methods {
122 my ($self, @methods) = @_;
123 $self->get_required_methods_map->{$_} = undef foreach @methods;
124}
125
38f1204c 126sub remove_required_methods {
127 my ($self, @methods) = @_;
128 delete $self->get_required_methods_map->{$_} foreach @methods;
129}
130
1331430a 131sub get_required_method_list {
132 my ($self) = @_;
133 keys %{$self->get_required_methods_map};
134}
135
136sub requires_method {
137 my ($self, $method_name) = @_;
138 exists $self->get_required_methods_map->{$method_name} ? 1 : 0;
139}
140
db1ab48d 141sub _clean_up_required_methods {
142 my $self = shift;
143 foreach my $method ($self->get_required_method_list) {
38f1204c 144 $self->remove_required_methods($method)
db1ab48d 145 if $self->has_method($method);
146 }
147}
148
80572233 149## methods
150
40e89659 151# FIXME:
152# Yes, this is a really really UGLY hack
153# but it works, and until I can figure
154# out a better way, this is gonna be it.
155
68efb014 156sub get_method { (shift)->Moose::Meta::Class::get_method(@_) }
157sub find_method_by_name { (shift)->Moose::Meta::Class::find_method_by_name(@_) }
158sub has_method { (shift)->Moose::Meta::Class::has_method(@_) }
159sub alias_method { (shift)->Moose::Meta::Class::alias_method(@_) }
e185c027 160sub get_method_list {
161 my ($self) = @_;
bdabd620 162 grep {
163 # NOTE:
164 # this is a kludge for now,... these functions
165 # should not be showing up in the list at all,
166 # but they do, so we need to switch Moose::Role
167 # and Moose to use Sub::Exporter to prevent this
1331430a 168 !/^(meta|has|extends|blessed|confess|augment|inner|override|super|before|after|around|with|requires)$/
68efb014 169 } $self->Moose::Meta::Class::get_method_list;
e185c027 170}
171
172# ... however the items in statis (attributes & method modifiers)
173# can be removed and added to through this API
174
175# attributes
176
177sub add_attribute {
a2eec5e7 178 my $self = shift;
179 my $name = shift;
180 my $attr_desc;
181 if (scalar @_ == 1 && ref($_[0]) eq 'HASH') {
182 $attr_desc = $_[0];
183 }
184 else {
185 $attr_desc = { @_ };
186 }
187 $self->get_attribute_map->{$name} = $attr_desc;
e185c027 188}
189
190sub has_attribute {
191 my ($self, $name) = @_;
192 exists $self->get_attribute_map->{$name} ? 1 : 0;
193}
194
195sub get_attribute {
196 my ($self, $name) = @_;
197 $self->get_attribute_map->{$name}
198}
199
200sub remove_attribute {
201 my ($self, $name) = @_;
202 delete $self->get_attribute_map->{$name}
203}
204
205sub get_attribute_list {
206 my ($self) = @_;
207 keys %{$self->get_attribute_map};
208}
209
0558683c 210# method modifiers
211
212# mimic the metaclass API
213sub add_before_method_modifier { (shift)->_add_method_modifier('before', @_) }
214sub add_around_method_modifier { (shift)->_add_method_modifier('around', @_) }
215sub add_after_method_modifier { (shift)->_add_method_modifier('after', @_) }
216
217sub _add_method_modifier {
218 my ($self, $modifier_type, $method_name, $method) = @_;
219 my $accessor = "get_${modifier_type}_method_modifiers_map";
220 $self->$accessor->{$method_name} = []
221 unless exists $self->$accessor->{$method_name};
222 my $modifiers = $self->$accessor->{$method_name};
223 # NOTE:
224 # check to see that we aren't adding the
225 # same code twice. We err in favor of the
226 # first on here, this may not be as expected
227 foreach my $modifier (@{$modifiers}) {
228 return if $modifier == $method;
229 }
230 push @{$modifiers} => $method;
231}
232
233sub add_override_method_modifier {
234 my ($self, $method_name, $method) = @_;
235 (!$self->has_method($method_name))
236 || confess "Cannot add an override of method '$method_name' " .
237 "because there is a local version of '$method_name'";
238 $self->get_override_method_modifiers_map->{$method_name} = $method;
239}
240
241sub has_before_method_modifiers { (shift)->_has_method_modifiers('before', @_) }
242sub has_around_method_modifiers { (shift)->_has_method_modifiers('around', @_) }
243sub has_after_method_modifiers { (shift)->_has_method_modifiers('after', @_) }
244
245# override just checks for one,..
246# but we can still re-use stuff
247sub has_override_method_modifier { (shift)->_has_method_modifiers('override', @_) }
248
249sub _has_method_modifiers {
250 my ($self, $modifier_type, $method_name) = @_;
251 my $accessor = "get_${modifier_type}_method_modifiers_map";
252 # NOTE:
253 # for now we assume that if it exists,..
254 # it has at least one modifier in it
255 (exists $self->$accessor->{$method_name}) ? 1 : 0;
256}
257
258sub get_before_method_modifiers { (shift)->_get_method_modifiers('before', @_) }
259sub get_around_method_modifiers { (shift)->_get_method_modifiers('around', @_) }
260sub get_after_method_modifiers { (shift)->_get_method_modifiers('after', @_) }
261
262sub _get_method_modifiers {
263 my ($self, $modifier_type, $method_name) = @_;
264 my $accessor = "get_${modifier_type}_method_modifiers_map";
265 @{$self->$accessor->{$method_name}};
266}
267
268sub get_override_method_modifier {
269 my ($self, $method_name) = @_;
270 $self->get_override_method_modifiers_map->{$method_name};
271}
272
273sub get_method_modifier_list {
274 my ($self, $modifier_type) = @_;
275 my $accessor = "get_${modifier_type}_method_modifiers_map";
276 keys %{$self->$accessor};
277}
e185c027 278
bdabd620 279## applying a role to a class ...
280
a2eec5e7 281sub _check_excluded_roles {
bdabd620 282 my ($self, $other) = @_;
d79e62fd 283 if ($other->excludes_role($self->name)) {
284 confess "Conflict detected: " . $other->name . " excludes role '" . $self->name . "'";
285 }
d79e62fd 286 foreach my $excluded_role_name ($self->get_excluded_roles_list) {
9c429218 287 if ($other->does_role($excluded_role_name)) {
d79e62fd 288 confess "The class " . $other->name . " does the excluded role '$excluded_role_name'";
289 }
290 else {
291 if ($other->isa('Moose::Meta::Role')) {
d79e62fd 292 $other->add_excluded_roles($excluded_role_name);
293 }
a2eec5e7 294 # else -> ignore it :)
d79e62fd 295 }
296 }
a2eec5e7 297}
298
299sub _check_required_methods {
300 my ($self, $other) = @_;
1331430a 301 # NOTE:
302 # we might need to move this down below the
303 # the attributes so that we can require any
304 # attribute accessors. However I am thinking
305 # that maybe those are somehow exempt from
306 # the require methods stuff.
307 foreach my $required_method_name ($self->get_required_method_list) {
8c835eba 308
be4427d0 309 unless ($other->find_method_by_name($required_method_name)) {
fa1be058 310 if ($other->isa('Moose::Meta::Role')) {
311 $other->add_required_methods($required_method_name);
312 }
313 else {
314 confess "'" . $self->name . "' requires the method '$required_method_name' " .
315 "to be implemented by '" . $other->name . "'";
316 }
317 }
0558683c 318 else {
319 # NOTE:
320 # we need to make sure that the method is
321 # not a method modifier, because those do
322 # not satisfy the requirements ...
323 my $method = $other->get_method($required_method_name);
324 # check if it is an override or a generated accessor ..
325 (!$method->isa('Moose::Meta::Method::Overriden') &&
326 !$method->isa('Class::MOP::Attribute::Accessor'))
327 || confess "'" . $self->name . "' requires the method '$required_method_name' " .
328 "to be implemented by '" . $other->name . "', the method is only a method modifier";
329 # before/after/around methods are a little trickier
330 # since we wrap the original local method (if applicable)
331 # so we need to check if the original wrapped method is
332 # from the same package, and not a wrap of the super method
333 if ($method->isa('Class::MOP::Method::Wrapped')) {
334 ($method->get_original_method->package_name eq $other->name)
335 || confess "'" . $self->name . "' requires the method '$required_method_name' " .
336 "to be implemented by '" . $other->name . "', the method is only a method modifier";
337 }
338 }
a2eec5e7 339 }
340}
341
342sub _apply_attributes {
343 my ($self, $other) = @_;
bdabd620 344 foreach my $attribute_name ($self->get_attribute_list) {
db1ab48d 345 # it if it has one already
a2eec5e7 346 if ($other->has_attribute($attribute_name) &&
347 # make sure we haven't seen this one already too
348 $other->get_attribute($attribute_name) != $self->get_attribute($attribute_name)) {
db1ab48d 349 # see if we are being composed
350 # into a role or not
a2eec5e7 351 if ($other->isa('Moose::Meta::Role')) {
db1ab48d 352 # all attribute conflicts between roles
353 # result in an immediate fatal error
354 confess "Role '" . $self->name . "' has encountered an attribute conflict " .
355 "during composition. This is fatal error and cannot be disambiguated.";
356 }
357 else {
358 # but if this is a class, we
359 # can safely skip adding the
360 # attribute to the class
361 next;
362 }
363 }
364 else {
db1ab48d 365 $other->add_attribute(
366 $attribute_name,
a2eec5e7 367 $self->get_attribute($attribute_name)
db1ab48d 368 );
369 }
a2eec5e7 370 }
371}
372
373sub _apply_methods {
374 my ($self, $other) = @_;
bdabd620 375 foreach my $method_name ($self->get_method_list) {
db1ab48d 376 # it if it has one already
d30bc041 377 if ($other->has_method($method_name) &&
378 # and if they are not the same thing ...
379 $other->get_method($method_name) != $self->get_method($method_name)) {
db1ab48d 380 # see if we are composing into a role
381 if ($other->isa('Moose::Meta::Role')) {
382 # method conflicts between roles result
383 # in the method becoming a requirement
384 $other->add_required_methods($method_name);
385 # NOTE:
386 # we have to remove the method from our
387 # role, if this is being called from combine()
388 # which means the meta is an anon class
389 # this *may* cause problems later, but it
390 # is probably fairly safe to assume that
391 # anon classes will only be used internally
392 # or by people who know what they are doing
68efb014 393 $other->Moose::Meta::Class::remove_method($method_name)
40e89659 394 if $other->name =~ /__COMPOSITE_ROLE_SANDBOX__/;
db1ab48d 395 }
396 else {
397 next;
398 }
399 }
400 else {
401 # add it, although it could be overriden
402 $other->alias_method(
403 $method_name,
404 $self->get_method($method_name)
405 );
406 }
a2eec5e7 407 }
408}
409
0558683c 410sub _apply_override_method_modifiers {
411 my ($self, $other) = @_;
412 foreach my $method_name ($self->get_method_modifier_list('override')) {
413 # it if it has one already then ...
414 if ($other->has_method($method_name)) {
415 # if it is being composed into another role
416 # we have a conflict here, because you cannot
417 # combine an overriden method with a locally
418 # defined one
419 if ($other->isa('Moose::Meta::Role')) {
420 confess "Role '" . $self->name . "' has encountered an 'override' method conflict " .
421 "during composition (A local method of the same name as been found). This " .
422 "is fatal error.";
423 }
424 else {
425 # if it is a class, then we
426 # just ignore this here ...
427 next;
428 }
429 }
430 else {
431 # if no local method is found, then we
432 # must check if we are a role or class
433 if ($other->isa('Moose::Meta::Role')) {
434 # if we are a role, we need to make sure
435 # we dont have a conflict with the role
436 # we are composing into
437 if ($other->has_override_method_modifier($method_name) &&
438 $other->get_override_method_modifier($method_name) != $self->get_override_method_modifier($method_name)) {
439 confess "Role '" . $self->name . "' has encountered an 'override' method conflict " .
440 "during composition (Two 'override' methods of the same name encountered). " .
441 "This is fatal error.";
442 }
443 else {
444 # if there is no conflict,
445 # just add it to the role
446 $other->add_override_method_modifier(
447 $method_name,
448 $self->get_override_method_modifier($method_name)
449 );
450 }
451 }
452 else {
453 # if this is not a role, then we need to
454 # find the original package of the method
455 # so that we can tell the class were to
456 # find the right super() method
457 my $method = $self->get_override_method_modifier($method_name);
458 my $package = svref_2object($method)->GV->STASH->NAME;
459 # if it is a class, we just add it
460 $other->add_override_method_modifier($method_name, $method, $package);
461 }
462 }
463 }
464}
465
466sub _apply_method_modifiers {
467 my ($self, $modifier_type, $other) = @_;
468 my $add = "add_${modifier_type}_method_modifier";
469 my $get = "get_${modifier_type}_method_modifiers";
470 foreach my $method_name ($self->get_method_modifier_list($modifier_type)) {
471 $other->$add(
472 $method_name,
473 $_
474 ) foreach $self->$get($method_name);
475 }
476}
477
478sub _apply_before_method_modifiers { (shift)->_apply_method_modifiers('before' => @_) }
479sub _apply_around_method_modifiers { (shift)->_apply_method_modifiers('around' => @_) }
480sub _apply_after_method_modifiers { (shift)->_apply_method_modifiers('after' => @_) }
481
b805c70c 482my $anon_counter = 0;
483
a2eec5e7 484sub apply {
485 my ($self, $other) = @_;
bdabd620 486
b805c70c 487 unless ($other->isa('Moose::Meta::Class') || $other->isa('Moose::Meta::Role')) {
488
489 # Runtime Role mixins
490
491 # FIXME:
492 # We really should do this better, and
493 # cache the results of our efforts so
494 # that we don't need to repeat them.
495
496 my $pkg_name = __PACKAGE__ . "::__RUNTIME_ROLE_ANON_CLASS__::" . $anon_counter++;
497 eval "package " . $pkg_name . "; our \$VERSION = '0.00';";
498 die $@ if $@;
499
500 my $object = $other;
501
502 $other = Moose::Meta::Class->initialize($pkg_name);
503 $other->superclasses(blessed($object));
504
505 bless $object => $pkg_name;
506 }
d7c04559 507
a2eec5e7 508 $self->_check_excluded_roles($other);
509 $self->_check_required_methods($other);
510
511 $self->_apply_attributes($other);
0558683c 512 $self->_apply_methods($other);
b805c70c 513
0558683c 514 $self->_apply_override_method_modifiers($other);
515 $self->_apply_before_method_modifiers($other);
516 $self->_apply_around_method_modifiers($other);
517 $self->_apply_after_method_modifiers($other);
d63f8289 518
bdabd620 519 $other->add_role($self);
520}
521
db1ab48d 522sub combine {
523 my ($class, @roles) = @_;
524
40e89659 525 my $pkg_name = __PACKAGE__ . "::__COMPOSITE_ROLE_SANDBOX__::" . $anon_counter++;
68efb014 526 eval "package " . $pkg_name . "; our \$VERSION = '0.00';";
527 die $@ if $@;
528
529 my $combined = $class->initialize($pkg_name);
db1ab48d 530
531 foreach my $role (@roles) {
532 $role->apply($combined);
533 }
534
d05cd563 535 $combined->_clean_up_required_methods;
db1ab48d 536
537 return $combined;
538}
539
a7d0cd00 540package Moose::Meta::Role::Method;
541
542use strict;
543use warnings;
544
545our $VERSION = '0.01';
546
547use base 'Class::MOP::Method';
e185c027 548
5491;
550
551__END__
552
553=pod
554
555=head1 NAME
556
557Moose::Meta::Role - The Moose Role metaclass
558
559=head1 DESCRIPTION
560
79592a54 561Moose's Roles are being actively developed, please see L<Moose::Role>
02a0fb52 562for more information. For the most part, this has no user-serviceable
563parts inside. It's API is still subject to some change (although
564probably not that much really).
79592a54 565
e185c027 566=head1 METHODS
567
568=over 4
569
570=item B<meta>
571
572=item B<new>
573
78cd1d3b 574=item B<apply>
575
db1ab48d 576=item B<combine>
577
e185c027 578=back
579
580=over 4
581
582=item B<name>
583
584=item B<version>
585
586=item B<role_meta>
587
588=back
589
590=over 4
591
80572233 592=item B<get_roles>
593
594=item B<add_role>
595
596=item B<does_role>
597
598=back
599
600=over 4
601
d79e62fd 602=item B<add_excluded_roles>
603
604=item B<excludes_role>
605
606=item B<get_excluded_roles_list>
607
608=item B<get_excluded_roles_map>
609
2b14ac61 610=item B<calculate_all_roles>
611
d79e62fd 612=back
613
614=over 4
615
68efb014 616=item B<method_metaclass>
617
be4427d0 618=item B<find_method_by_name>
619
e185c027 620=item B<get_method>
621
622=item B<has_method>
623
bdabd620 624=item B<alias_method>
625
e185c027 626=item B<get_method_list>
627
628=back
629
630=over 4
631
632=item B<add_attribute>
633
634=item B<has_attribute>
635
636=item B<get_attribute>
637
638=item B<get_attribute_list>
639
640=item B<get_attribute_map>
641
642=item B<remove_attribute>
643
644=back
645
646=over 4
647
1331430a 648=item B<add_required_methods>
649
38f1204c 650=item B<remove_required_methods>
651
1331430a 652=item B<get_required_method_list>
653
654=item B<get_required_methods_map>
655
656=item B<requires_method>
657
658=back
659
0558683c 660=over 4
661
662=item B<add_after_method_modifier>
663
664=item B<add_around_method_modifier>
665
666=item B<add_before_method_modifier>
667
668=item B<add_override_method_modifier>
669
670=over 4
671
672=back
673
674=item B<has_after_method_modifiers>
675
676=item B<has_around_method_modifiers>
677
678=item B<has_before_method_modifiers>
679
680=item B<has_override_method_modifier>
681
682=over 4
683
684=back
685
686=item B<get_after_method_modifiers>
687
688=item B<get_around_method_modifiers>
689
690=item B<get_before_method_modifiers>
691
692=item B<get_method_modifier_list>
693
694=over 4
695
696=back
697
698=item B<get_override_method_modifier>
699
700=item B<get_after_method_modifiers_map>
701
702=item B<get_around_method_modifiers_map>
703
704=item B<get_before_method_modifiers_map>
705
706=item B<get_override_method_modifiers_map>
707
708=back
709
e185c027 710=head1 BUGS
711
712All complex software has bugs lurking in it, and this module is no
713exception. If you find a bug please either email me, or add the bug
714to cpan-RT.
715
716=head1 AUTHOR
717
718Stevan Little E<lt>stevan@iinteractive.comE<gt>
719
720=head1 COPYRIGHT AND LICENSE
721
722Copyright 2006 by Infinity Interactive, Inc.
723
724L<http://www.iinteractive.com>
725
726This library is free software; you can redistribute it and/or modify
727it under the same terms as Perl itself.
728
b8aeb4dc 729=cut