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