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